From 653748363f74e04720b05836fe0b21d0c5540bf7 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sun, 12 Jan 2025 14:42:54 -0600 Subject: [PATCH] tile options for slice9 and tile --- scripts/render.js | 20 ++- source/jsffi.c | 440 ++++++++++++++++++++++++++-------------------- 2 files changed, 265 insertions(+), 195 deletions(-) diff --git a/scripts/render.js b/scripts/render.js index 40131227..ca1626b5 100644 --- a/scripts/render.js +++ b/scripts/render.js @@ -959,7 +959,8 @@ function calc_image_size(img) return [img.texture.width*img.rect.width, img.texture.height*img.rect.height]; } -function tile(image, rect = [0,0], color = Color.white) + +function tile(image, rect = [0,0], color = Color.white, repeat = {}) { if (!image) throw Error ('Need an image to render.') if (typeof image === "string") @@ -1004,13 +1005,14 @@ render.images = function images(image, rects) for (var rect of rects) render.image(image,rect); } -render.tile = function(image, rect, color = Color.white, pipeline = sprite_pipeline) +var tile_def = {repeat_x:true, repeat_y:true}; +render.tile = function(image, rect, color = Color.white, tile = tile_def, pipeline = sprite_pipeline) { if (!image) throw Error ('Need an image to render.') if (typeof image === "string") image = game.texture(image); - var mesh = render._main.tile(image.texture, {x:0,y:0,width:image.texture.width,height:image.texture.height}, rect); + var mesh = render._main.tile(image.texture, {x:0,y:0,width:image.texture.width,height:image.texture.height}, rect, tile); current_queue.push({ type:'geometry', mesh, @@ -1022,12 +1024,20 @@ render.tile = function(image, rect, color = Color.white, pipeline = sprite_pipel } // slice is given in pixels -render.slice9 = function slice9(image, rect = [0,0], slice = 0, color = Color.white, pipeline = sprite_pipeline) { +var slice9_info = { + tile_top:true, + tile_bottom:true, + tile_left:true, + tile_right:true, + tile_center_x:true, + tile_center_right:true +}; +render.slice9 = function slice9(image, rect = [0,0], slice = 0, color = Color.white, info = slice9_info, pipeline = sprite_pipeline) { if (!image) throw Error ('Need an image to render.') if (typeof image === "string") image = game.texture(image); - var mesh = render._main.slice9(image.texture, rect, clay.normalizeSpacing(slice)); + var mesh = render._main.slice9(image.texture, rect, clay.normalizeSpacing(slice), info); current_queue.push({ type: 'geometry', mesh, diff --git a/source/jsffi.c b/source/jsffi.c index 50fb745d..3187e061 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -4314,171 +4314,313 @@ JSC_CCALL(gpu_compute_pipeline, return SDL_GPUComputePipeline2js(js,pipeline); ) -static inline void add_quad(text_vert **verts, rect src, rect dst) +static inline void add_quad(text_vert **verts, rect *restrict src, rect *restrict dst) { text_vert v = (text_vert){ - .pos = (HMM_Vec2){dst.x, dst.y}, - .uv = (HMM_Vec2){src.x,src.y}, + .pos = (HMM_Vec2){dst->x, dst->y}, + .uv = (HMM_Vec2){src->x,src->y}, .color = (HMM_Vec4){1,1,1,1} }; arrput(*verts, v); v = (text_vert){ - .pos = (HMM_Vec2){dst.x+dst.w, dst.y}, - .uv = (HMM_Vec2){src.x+src.w,src.y}, + .pos = (HMM_Vec2){dst->x+dst->w, dst->y}, + .uv = (HMM_Vec2){src->x+src->w,src->y}, .color = (HMM_Vec4){1,1,1,1} }; arrput(*verts, v); v = (text_vert){ - .pos = (HMM_Vec2){dst.x, dst.y+dst.h}, - .uv = (HMM_Vec2){src.x,src.y+src.h}, + .pos = (HMM_Vec2){dst->x, dst->y+dst->h}, + .uv = (HMM_Vec2){src->x,src->y+src->h}, .color = (HMM_Vec4){1,1,1,1} }; arrput(*verts, v); v = (text_vert){ - .pos = (HMM_Vec2){dst.x+dst.w, dst.y+dst.h}, - .uv = (HMM_Vec2){src.x+src.w,src.y+src.h}, + .pos = (HMM_Vec2){dst->x+dst->w, dst->y+dst->h}, + .uv = (HMM_Vec2){src->x+src->w,src->y+src->h}, .color = (HMM_Vec4){1,1,1,1} }; arrput(*verts, v); } +static inline void tile_region(text_vert **verts, rect src_uv, rect dst, float tex_w, float tex_h, bool tile_x, bool tile_y) +{ + // Convert the incoming UV rect into pixel coords + rect src_px = { + .x = src_uv.x * tex_w, + .y = src_uv.y * tex_h, + .w = src_uv.w * tex_w, + .h = src_uv.h * tex_h + }; + + // If src_px or dst is degenerate, early out + if (src_px.w <= 0.0f || src_px.h <= 0.0f || dst.w <= 0.0f || dst.h <= 0.0f) + return; + + // How many full tiles horizontally/vertically? + // If not tiling in a dimension, we treat it as exactly 1 tile (no leftover). + float cols_f, rows_f; + float remain_wf = 0.0f; + float remain_hf = 0.0f; + + if (tile_x) { + remain_wf = modff(dst.w / src_px.w, &cols_f); + } else { + // Only 1 "column" covering entire width, no leftover + cols_f = 1.0f; + remain_wf = 0.0f; + } + + if (tile_y) { + remain_hf = modff(dst.h / src_px.h, &rows_f); + } else { + // Only 1 "row" covering entire height, no leftover + rows_f = 1.0f; + remain_hf = 0.0f; + } + + int cols = (int)cols_f; + int rows = (int)rows_f; + + // The leftover portion in screen coords (pixels) + float remain_dst_w = remain_wf * src_px.w; + float remain_dst_h = remain_hf * src_px.h; + + // Build the UV rect for a “full” tile + rect src_full = src_uv; + + // Partial leftover in UV + float remain_src_w_uv = remain_dst_w / tex_w; + float remain_src_h_uv = remain_dst_h / tex_h; + + // For partial leftover in X dimension + rect src_partial_x = src_full; + src_partial_x.w = remain_src_w_uv; + + // For partial leftover in Y dimension + rect src_partial_y = src_full; + src_partial_y.h = remain_src_h_uv; + + // For partial leftover in both X & Y + rect src_partial_xy = src_full; + src_partial_xy.w = remain_src_w_uv; + src_partial_xy.h = remain_src_h_uv; + + // Each tile is drawn 1:1 in screen coords + float tile_w = tile_x ? src_px.w : dst.w; // If not tiling horizontally, match the entire dst width + float tile_h = tile_y ? src_px.h : dst.h; // If not tiling vertically, match the entire dst height + + rect curr_dst; + curr_dst.w = tile_w; + curr_dst.h = tile_h; + curr_dst.y = dst.y; + + // Loop over rows + for (int y = 0; y < rows; y++) { + curr_dst.x = dst.x; + + // Loop over columns + for (int x = 0; x < cols; x++) { + add_quad(verts, &src_full, &curr_dst); + curr_dst.x += tile_w; + } + + // Right-side leftover tile (only if tile_x is true) + if (tile_x && remain_dst_w > 0.0f) { + rect partial_dst = { + .x = curr_dst.x, .y = curr_dst.y, + .w = remain_dst_w, .h = tile_h + }; + add_quad(verts, &src_partial_x, &partial_dst); + } + curr_dst.y += tile_h; + } + + // Bottom leftover row (only if tile_y is true) + if (tile_y && remain_dst_h > 0.0f) { + rect partial_row_dst; + partial_row_dst.w = tile_w; + partial_row_dst.h = remain_dst_h; + partial_row_dst.y = curr_dst.y; + partial_row_dst.x = dst.x; + + // Full columns in leftover row + for (int x = 0; x < cols; x++) { + add_quad(verts, &src_partial_y, &partial_row_dst); + partial_row_dst.x += tile_w; + } + + // Partial leftover corner (both X & Y leftover) + if (tile_x && remain_dst_w > 0.0f) { + rect partial_corner_dst = { + .x = partial_row_dst.x, .y = partial_row_dst.y, + .w = remain_dst_w, .h = remain_dst_h + }; + add_quad(verts, &src_partial_xy, &partial_corner_dst); + } + } +} + JSC_CCALL(gpu_slice9, JSValue jstex = argv[0]; - rect dst = js2rect(js,argv[1]); - rect src = (rect){ - .x = 0, - .y = 0, - .w = 1, - .h = 1 + rect dst = js2rect(js, argv[1]); + + // Full texture in UV coords + rect src = { + .x = 0, .y = 0, + .w = 1, .h = 1 }; - lrtb src_slice = js2lrtb(js,argv[2]); + + // The “slice” LRTB in PIXELS, but we convert to UV below + lrtb src_slice = js2lrtb(js, argv[2]); lrtb dst_slice = src_slice; HMM_Vec2 size; - JS_GETPROP(js,size.x,jstex, width, number) - JS_GETPROP(js,size.y,jstex,height,number) - -/* src.x /= size.x; - src.w /= size.x; - src.y /= size.y; - src.h /= size.y;*/ + JS_GETPROP(js, size.x, jstex, width, number) + JS_GETPROP(js, size.y, jstex, height, number) + + JSValue info = argv[3]; + int tile_top, tile_bottom, tile_left, tile_right, center_x, center_y; + JS_GETPROP(js,tile_top, info, tile_top, bool) + JS_GETPROP(js,tile_bottom,info,tile_bottom,bool) + JS_GETPROP(js,tile_left,info,tile_left,bool) + JS_GETPROP(js,tile_right,info,tile_right,bool) + JS_GETPROP(js, center_x, info, tile_center_x, bool) + JS_GETPROP(js, center_y, info, tile_center_y, bool) + + // Convert the slice edges from pixel to UV src_slice.l /= size.x; src_slice.r /= size.x; src_slice.t /= size.y; src_slice.b /= size.y; text_vert *verts = NULL; - rect curr_src; rect curr_dst; - // bottom left + // bottom-left corner (single quad) curr_src = src; curr_src.w = src_slice.l; curr_src.h = src_slice.b; + curr_dst = dst; curr_dst.w = dst_slice.l; curr_dst.h = dst_slice.b; - add_quad(&verts,curr_src, curr_dst); + add_quad(&verts, &curr_src, &curr_dst); - // top left + // top-left corner (single quad) curr_src = src; - curr_dst = dst; + curr_src.x = src.x; curr_src.y = src.y + src.h - src_slice.t; - curr_src.h = src_slice.t; curr_src.w = src_slice.l; - curr_dst.y = dst.y + dst.h - dst_slice.t; - curr_dst.h = dst_slice.t; - curr_dst.w = dst_slice.l; - add_quad(&verts,curr_src,curr_dst); - - // left bar - curr_src = src; - curr_dst = dst; - curr_src.y = src.y + src_slice.b; - curr_src.h = src.h - src_slice.t - src_slice.b; - curr_src.w = src_slice.l; - curr_dst.y = dst.y + dst_slice.b; - curr_dst.h = dst.h - dst_slice.t - dst_slice.b; - curr_dst.w = dst_slice.l; - add_quad(&verts,curr_src,curr_dst); - - // bottom bar - curr_src = src; - curr_dst = dst; - curr_src.x = src.x + src_slice.l; - curr_src.h = src_slice.b; - curr_src.w = src.w - src_slice.l - src_slice.r; - curr_dst.x = dst.x + dst_slice.l; - curr_dst.h = dst_slice.b; - curr_dst.w = dst.w - dst_slice.l - dst_slice.r; - add_quad(&verts,curr_src,curr_dst); - - // top bar - curr_src = src; - curr_dst = dst; - curr_src.x = src.x + src_slice.l; curr_src.h = src_slice.t; - curr_src.y = src.y + src.h - src_slice.t; - curr_src.w = src.w - src_slice.l - src_slice.r; - curr_dst.x = dst.x + dst_slice.l; - curr_dst.h = dst_slice.t; + + curr_dst = dst; + curr_dst.x = dst.x; curr_dst.y = dst.y + dst.h - dst_slice.t; - curr_dst.w = dst.w - dst_slice.l - dst_slice.r; - add_quad(&verts,curr_src,curr_dst); + curr_dst.w = dst_slice.l; + curr_dst.h = dst_slice.t; + add_quad(&verts, &curr_src, &curr_dst); - // center + // bottom-right corner (single quad) curr_src = src; - curr_dst = dst; - curr_src.x = src.x + src_slice.l; - curr_src.w = src.w - src_slice.l - src_slice.r; - curr_src.y = src.y + src_slice.b; - curr_src.h = src.h - src_slice.t - src_slice.b; - curr_dst.x = dst.x + dst_slice.l; - curr_dst.w = dst.w - dst_slice.l - dst_slice.r; - curr_dst.y = dst.y + dst_slice.b; - curr_dst.h = dst.h - dst_slice.t - dst_slice.b; - add_quad(&verts, curr_src, curr_dst); - - // bottom right - curr_src = src; - curr_dst = dst; curr_src.x = src.x + src.w - src_slice.r; + curr_src.y = src.y; curr_src.w = src_slice.r; curr_src.h = src_slice.b; + + curr_dst = dst; curr_dst.x = dst.x + dst.w - dst_slice.r; + curr_dst.y = dst.y; curr_dst.w = dst_slice.r; curr_dst.h = dst_slice.b; - add_quad(&verts, curr_src, curr_dst); + add_quad(&verts, &curr_src, &curr_dst); - // top right + // top-right corner (single quad) curr_src = src; - curr_dst = dst; curr_src.x = src.x + src.w - src_slice.r; - curr_src.w = src_slice.r; curr_src.y = src.y + src.h - src_slice.t; - curr_src.h = src_slice.t; - curr_dst.x = dst.x + dst.w - dst_slice.r; - curr_dst.w = dst_slice.r; - curr_dst.y = dst.y + dst.h - dst_slice.t; - curr_dst.h = dst_slice.t; - add_quad(&verts, curr_src, curr_dst); - - // right bar - curr_src = src; - curr_dst = dst; - curr_src.x = src.x + src.w - src_slice.r; curr_src.w = src_slice.r; - curr_src.y = src.y + src_slice.b; - curr_src.h = src.h - src_slice.t - src_slice.b; + curr_src.h = src_slice.t; + + curr_dst = dst; curr_dst.x = dst.x + dst.w - dst_slice.r; + curr_dst.y = dst.y + dst.h - dst_slice.t; curr_dst.w = dst_slice.r; + curr_dst.h = dst_slice.t; + add_quad(&verts, &curr_src, &curr_dst); + + // left bar (tiled) + curr_src = src; + curr_src.x = src.x; + curr_src.y = src.y + src_slice.b; + curr_src.w = src_slice.l; + curr_src.h = src.h - src_slice.t - src_slice.b; + + curr_dst = dst; + curr_dst.x = dst.x; curr_dst.y = dst.y + dst_slice.b; + curr_dst.w = dst_slice.l; curr_dst.h = dst.h - dst_slice.t - dst_slice.b; - add_quad(&verts, curr_src, curr_dst); + tile_region(&verts, curr_src, curr_dst, size.x, size.y,tile_left,tile_left); + + // right bar (tiled) + curr_src = src; + curr_src.x = src.x + src.w - src_slice.r; + curr_src.y = src.y + src_slice.b; + curr_src.w = src_slice.r; + curr_src.h = src.h - src_slice.t - src_slice.b; + + curr_dst = dst; + curr_dst.x = dst.x + dst.w - dst_slice.r; + curr_dst.y = dst.y + dst_slice.b; + curr_dst.w = dst_slice.r; + curr_dst.h = dst.h - dst_slice.t - dst_slice.b; + tile_region(&verts, curr_src, curr_dst, size.x, size.y,tile_right,tile_right); + + // bottom bar (tiled) + curr_src = src; + curr_src.x = src.x + src_slice.l; + curr_src.y = src.y; + curr_src.w = src.w - src_slice.l - src_slice.r; + curr_src.h = src_slice.b; + + curr_dst = dst; + curr_dst.x = dst.x + dst_slice.l; + curr_dst.y = dst.y; + curr_dst.w = dst.w - dst_slice.l - dst_slice.r; + curr_dst.h = dst_slice.b; + tile_region(&verts, curr_src, curr_dst, size.x, size.y,tile_bottom,tile_bottom); + + // top bar (tiled) + curr_src = src; + curr_src.x = src.x + src_slice.l; + curr_src.y = src.y + src.h - src_slice.t; + curr_src.w = src.w - src_slice.l - src_slice.r; + curr_src.h = src_slice.t; + + curr_dst = dst; + curr_dst.x = dst.x + dst_slice.l; + curr_dst.y = dst.y + dst.h - dst_slice.t; + curr_dst.w = dst.w - dst_slice.l - dst_slice.r; + curr_dst.h = dst_slice.t; + tile_region(&verts, curr_src, curr_dst, size.x, size.y,tile_top,tile_top); + + // center (tiled) + curr_src = src; + curr_src.x = src.x + src_slice.l; + curr_src.y = src.y + src_slice.b; + curr_src.w = src.w - src_slice.l - src_slice.r; + curr_src.h = src.h - src_slice.t - src_slice.b; + + curr_dst = dst; + curr_dst.x = dst.x + dst_slice.l; + curr_dst.y = dst.y + dst_slice.b; + curr_dst.w = dst.w - dst_slice.l - dst_slice.r; + curr_dst.h = dst.h - dst_slice.t - dst_slice.b; + tile_region(&verts, curr_src, curr_dst, size.x, size.y, center_x,center_y); JSValue mesh = quads_to_mesh(js, verts); arrfree(verts); @@ -4493,96 +4635,14 @@ JSC_CCALL(gpu_tile, rect src_pixels = js2rect(js, argv[1]); // 'src' as pixel dimensions rect dst = js2rect(js, argv[2]); // 'dst' as screen coords - - // Determine how many full tiles horizontally/vertically - float fcols, frows; - float remaining_w = modff(dst.w / src_pixels.w, &fcols); - float remaining_h = modff(dst.h / src_pixels.h, &frows); - - // The partial leftover in pixel dimensions - float remain_src_w = remaining_w * src_pixels.w; - float remain_src_h = remaining_h * src_pixels.h; - - // The corresponding leftover area in screen coords (since src -> 1:1 draw) - float remain_dst_w = remain_src_w; - float remain_dst_h = remain_src_h; - - int cols = (int)fcols; - int rows = (int)frows; - - // Convert the whole source rect from pixels into UV coords - // so add_quad can draw the correct portion of the texture. - rect src_full; - src_full.x = src_pixels.x / size.x; - src_full.y = src_pixels.y / size.y; - src_full.w = src_pixels.w / size.x; - src_full.h = src_pixels.h / size.y; - - // For partial leftover in UV coords - float remain_src_w_uv = remain_src_w / size.x; - float remain_src_h_uv = remain_src_h / size.y; - - // We'll set up some "partial" src rect variants: - rect src_partial_x = src_full; // partial in the X dimension - src_partial_x.w = remain_src_w_uv; - - rect src_partial_y = src_full; // partial in the Y dimension - src_partial_y.h = remain_src_h_uv; - - rect src_partial_xy = src_full; // partial in both X and Y - src_partial_xy.w = remain_src_w_uv; - src_partial_xy.h = remain_src_h_uv; - - // Now we tile in screen coords. Each tile is as wide/high as src_pixels (1:1). - // The UV coords for a "full" tile use src_full; - // for partial leftover tiles, we use the partial variants above. + + int tilex, tiley; + JSValue jstile = argv[3]; + JS_GETPROP(js,tilex,jstile,repeat_x,bool) + JS_GETPROP(js,tiley,jstile,repeat_y,bool) + text_vert *verts = NULL; - - float tile_w = src_pixels.w; - float tile_h = src_pixels.h; - - rect curr_dst; - curr_dst.w = tile_w; - curr_dst.h = tile_h; - curr_dst.y = dst.y; - - // Place the full rows & columns - for (int y = 0; y < rows; y++) { - curr_dst.x = dst.x; - for (int x = 0; x < cols; x++) { - add_quad(&verts, src_full, curr_dst); - curr_dst.x += tile_w; - } - // Place the partial tile on the right edge, if any - if (remain_dst_w > 0.0f) { - rect partial_dst = { curr_dst.x, curr_dst.y, remain_dst_w, tile_h }; - add_quad(&verts, src_partial_x, partial_dst); - } - curr_dst.y += tile_h; - } - - // Now place the leftover row (if any) - if (remain_dst_h > 0.0f) { - rect partial_row_dst; - partial_row_dst.y = curr_dst.y; - partial_row_dst.w = tile_w; - partial_row_dst.h = remain_dst_h; - partial_row_dst.x = dst.x; - - // Full columns in this leftover row - for (int x = 0; x < cols; x++) { - add_quad(&verts, src_partial_y, partial_row_dst); - partial_row_dst.x += tile_w; - } - - // Partial corner tile: leftover on the right plus leftover row - if (remain_dst_w > 0.0f) { - rect partial_corner_dst = { partial_row_dst.x, partial_row_dst.y, - remain_dst_w, remain_dst_h }; - add_quad(&verts, src_partial_xy, partial_corner_dst); - } - } - + tile_region(&verts, src_pixels, dst, size.x, size.y,tilex,tiley); ret = quads_to_mesh(js,verts); arrfree(verts); ) @@ -4604,8 +4664,8 @@ static const JSCFunctionListEntry js_SDL_GPUDevice_funcs[] = { MIST_FUNC_DEF(gpu, wait_for_fences, 2), MIST_FUNC_DEF(gpu, query_fence, 1), MIST_FUNC_DEF(gpu, shader_format, 0), - MIST_FUNC_DEF(gpu, slice9, 4), - MIST_FUNC_DEF(gpu, tile, 3), + MIST_FUNC_DEF(gpu, slice9, 3), + MIST_FUNC_DEF(gpu, tile, 4), }; JSC_CCALL(renderpass_bind_pipeline,