From a0b610d93f5b038b41f614d5fd2b399a513159f8 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 24 Dec 2024 09:48:52 -0600 Subject: [PATCH] shaders --- scripts/render.js | 344 +++++--- shaders/common.hlsl | 15 - shaders/common/common.hlsl | 16 + shaders/common/model_pixel.hlsl | 10 + shaders/common/model_vertex.hlsl | 42 + shaders/common/pixel.hlsl | 8 + shaders/common/vertex.hlsl | 25 + shaders/compile.sh | 21 +- shaders/model.frag.hlsl | 11 +- shaders/model.vert.hlsl | 17 +- shaders/sprite.frag.hlsl | 13 +- shaders/sprite.vert.hlsl | 19 +- source/jsffi.c | 1321 ++++++++++++++++++------------ 13 files changed, 1137 insertions(+), 725 deletions(-) delete mode 100644 shaders/common.hlsl create mode 100644 shaders/common/common.hlsl create mode 100644 shaders/common/model_pixel.hlsl create mode 100644 shaders/common/model_vertex.hlsl create mode 100644 shaders/common/pixel.hlsl create mode 100644 shaders/common/vertex.hlsl diff --git a/scripts/render.js b/scripts/render.js index 48cc53cc..be415b3a 100644 --- a/scripts/render.js +++ b/scripts/render.js @@ -61,9 +61,9 @@ var base_pipeline = { primitive: "triangle", // point, line, linestrip, triangle, trianglestrip fill: true, // false for lines depth: { - compare: "always", // never/less/equal/less_equal/greater/not_equal/greater_equal/always + compare: "greater_equal", // never/less/equal/less_equal/greater/not_equal/greater_equal/always test: true, - write: false, + write: true, bias: 0, bias_slope_scale: 0, bias_clamp: 0 @@ -106,13 +106,68 @@ var base_pipeline = { label: "scripted pipeline" } -var pipeline_3d = Object.create(base_pipeline); +/* rendering modes + ps1 + gouraud + diffuse // 16 bit color, 5-5-5 + 7 dynamic lights, 1 ambient + textures are affine + no vertex skinning + 256x256 texture max (generally 128x128) + 320x240, variable up to 640x480 -function use_pipeline(pipeline) { - if (!pipeline.gpu) - pipeline.gpu = gpu.make_pipeline(pipeline); - render._main.set_pipeline(pipeline); -} + n64 + gouraud + diffuse + combiner // a secondary texture sometimes used to combine + 7 dynamic lights, 1 ambient + 320x240, or 640x480 + + sega saturn + gouraud + diffuse + 320x240 or 640x480 + + ps2 + phong + diffuse + combiner // second texture for modulation of diffuse + combine_mode // int for how to combine + + dreamcast + phong + diffuse + combiner // second texture; could be an environment map, or emboss bump mapping + fog + 640x480 + 640x448, special mode to 1280x1024 + + gamecube + phong + diffuse + +7 textures // wow! + 8 dynamic lights + 640x480 + +*/ + +/* meshes + position (float3) + color (rgba) + uv +*/ + +/* materials, modern pbr + any object can act as a "material". The engine expects some standardized things: + diffuse - base color texture + bump - a normal map for dot3 bump maping used in phong shading + height - a grayscale heightmap + occlusion - ambient occlusion texture + emission - texture for where model emits light + bump2 - a second normal map for detail + metallic - a metal/smoothness map + specular - specular map, alternative for the metallic workflow +*/ render.poly_prim = function poly_prim(verts) { var index = []; @@ -155,100 +210,92 @@ render.hotreload = function shader_hotreload() { } }; -function make_shader(shader, ...args) { - var file = `shaders/spirv/${shader}.spv`; - if (shader_cache[file]) return shader_cache[file]; - shader = io.slurpbytes(file); - shader = render._main.make_shader(shader, ...args); - shader_cache[file] = shader; - return shader; -} +function make_pipeline(pipeline) { + // 1) Reflection data for vertex shader + var refl = pipeline.vertex.reflection + if (!refl || !refl.inputs || !Array.isArray(refl.inputs)) { + // If there's no reflection data, just pass pipeline along + // or throw an error if reflection is mandatory + render._main.make_pipeline(pipeline) + return + } -function shader_apply_material(shader, material = {}, old = {}) { - render.setpipeline(cur.pipeline); - for (var p in shader.vs.unimap) { - if (!(p in material)) continue; - if (material[p] === old[p]) continue; - assert(p in material, `shader ${shader.name} has no uniform for ${p}`); - var s = shader.vs.unimap[p]; + var inputs = refl.inputs + var buffer_descriptions = [] + var attributes = [] - if (p === "bones") { - render.setunibones(0, s.slot, material[p]); - continue; + // 2) For each input in the reflection, build one buffer + attribute + // (Simplest approach: each input is stored in its own slot, offset=0) + for (var i = 0; i < inputs.length; i++) { + var inp = inputs[i] + var typeStr = inp.type + var nameStr = (inp.name || "").toUpperCase() + var pitch = 4 // fallback if unknown + var fmt = "float1" + + // Decide pitch & format based on 'type' + if (typeStr == "vec2") { + pitch = 8 + fmt = "float2" + } else if (typeStr == "vec3") { + pitch = 12 + fmt = "float3" + } else if (typeStr == "vec4") { + // Special case: if "COLOR" is in the name, treat it as packed bytes + if (nameStr.indexOf("COLOR") >= 0) { + pitch = 4 + fmt = "color" // signals engine to use SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4NORM + } else { + pitch = 16 + fmt = "float4" + } } - shader_unisize[s.size](0, s.slot, material[p]); + // Create a buffer description for this input + buffer_descriptions.push({ + slot: i, + pitch: pitch, + input_rate: "vertex", + instance_step_rate: 0, + name:inp.name.split(".").pop() + }) + + // Create a matching vertex attribute + attributes.push({ + location: inp.location, + buffer_slot: i, + format: fmt, + offset: 0 + }) } - for (var p in shader.fs.unimap) { - if (!(p in material)) continue; - if (material[p] === old[p]) continue; - assert(p in material, `shader ${shader.name} has no uniform for ${p}`); - var s = shader.fs.unimap[p]; - shader_unisize[s.size](1, s.slot, material[p]); - } + // 3) Attach these arrays onto the pipeline object + pipeline.vertex_buffer_descriptions = buffer_descriptions + pipeline.vertex_attributes = attributes - if (!material.diffuse) return; - if (material.diffuse === old.diffuse) return; - - if ("diffuse_texel" in shader.fs.unimap) render.setuniv2(1, shader.fs.unimap.diffuse_texel.slot, [1,1].div([material.diffuse.width, material.diffuse.height])); - - if ("diffuse_size" in shader.fs.unimap) render.setuniv2(1, shader.fs.unimap.diffuse_size.slot, [material.diffuse.width, material.diffuse.height]); - - if ("diffuse_size" in shader.vs.unimap) render.setuniv2(0, shader.vs.unimap.diffuse_size.slot, [material.diffuse.width, material.diffuse.height]); + // 4) Hand off the pipeline to native code + console.log(`depth: ${json.encode(pipeline.depth)}`); + return render._main.make_pipeline(pipeline) } -// Creates a binding object for a given mesh and shader -var bcache = new WeakMap(); -function sg_bind(mesh, ssbo) { - if (cur.bind && cur.mesh === mesh && cur.ssbo === ssbo) { - if (ssbo) - cur.bind.ssbo = [ssbo]; - else - cur.bind.ssbo = undefined; - cur.bind.images = cur.images; - cur.bind.samplers = cur.samplers; - render.setbind(cur.bind); - return; +function make_shader(sh_file) { + var file = `shaders/spirv/${sh_file}.spv` + var refl = json.decode(io.slurp(`shaders/reflection/${sh_file}.json`)) + if (shader_cache[file]) return shader_cache[file] + + var shader = { + code: io.slurpbytes(file), + stage: sh_file.endsWith("vert") ? "vertex" : "fragment", + num_samplers: refl.separate_samplers ? refl.separate_samplers.length : 0, + num_textures: 0, + num_storage_buffers: refl.separate_storage_buffers ? refl.separate_storage_buffers.length : 0, + num_uniform_buffers: refl.ubos ? refl.ubos.length : 0 } -/* if (bcache.has(cur.shader) && bcache.get(cur.shader).has(mesh)) { - cur.bind = bcache.get(cur.shader).get(mesh); - cur.bind.images = cur.images; - cur.bind.samplers = cur.samplers; - if (ssbo) - cur.bind.ssbo = [ssbo]; - render.setbind(cur.bind); - return; - }*/ - var bind = {};/* if (!bcache.has(cur.shader)) bcache.set(cur.shader, new WeakMap()); - if (!bcache.get(cur.shader).has(mesh)) bcache.get(cur.shader).set(mesh, bind);*/ - cur.mesh = mesh; - cur.ssbo = ssbo; - cur.bind = bind; - bind.attrib = []; - if (cur.shader.vs.inputs) - for (var a of cur.shader.vs.inputs) { - if (!(a.name in mesh)) { - console.error(`cannot draw shader ${cur.shader.name}; there is no attrib ${a.name} in the given mesh. ${json.encode(mesh)}`); - return undefined; - } else bind.attrib.push(mesh[a.name]); - } - - if (cur.shader.indexed) { - bind.index = mesh.index; - bind.count = mesh.count; - } else bind.count = mesh.verts; - - bind.ssbo = []; - if (cur.shader.vs.storage_buffers) for (var b of cur.shader.vs.storage_buffers) bind.ssbo.push(ssbo); - - bind.images = cur.images; - bind.samplers = cur.samplers; - - render.setbind(cur.bind); - - return bind; + shader.gpu = render._main.make_shader(shader) + shader.reflection = refl; + shader_cache[file] = shader + return shader } render.device = { @@ -290,7 +337,9 @@ var sprite_stack = []; render.device.doc = `Device resolutions given as [x,y,inches diagonal].`; var std_sampler; -function upload_model(model) +var tbuffer; +var spritemesh; +function upload_model(cmds, model) { var bufs = []; for (var i in model) { @@ -298,26 +347,58 @@ function upload_model(model) if (i === 'indices') model[i].index = true; bufs.push(model[i]); } - render._main.upload(bufs); + tbuffer = render._main.upload(cmds, bufs, tbuffer); } var campos = [0,0,500]; +function bind_model(pass, model, pipeline) +{ + var buffers = pipeline.vertex_buffer_descriptions; + var bufs = []; + for (var b of buffers) { + if (b.name in model) + bufs.push(model[b.name]) + else + throw Error (`could not find buffer ${b.name} on model`); + } + pass.bind_buffers(0,bufs); + pass.bind_index_buffer(model.indices); +} + +function bind_mat(pass, mat, pipeline) +{ + var imgs = []; + var refl = pipeline.fragment.reflection; + if (refl.separate_images) { + for (var i of pipeline.fragment.reflection.separate_images) { + if (i.name in mat) { + var tex = mat[i.name]; + imgs.push({texture:tex.texture, sampler:tex.sampler}); + } else + throw Error (`could not find all necessary images: ${i.name}`) + } + pass.bind_samplers(false, 0,imgs); + } +} + function gpupresent() { + var cmds = render._main.acquire_cmd_buffer(); var myimg = game.texture("pockle"); myimg.sampler = std_sampler; - var spritemesh = render._main.make_sprite_mesh(sprite_stack); - upload_model(spritemesh); - + spritemesh = render._main.make_sprite_mesh(sprite_stack, spritemesh); + upload_model(cmds, spritemesh); + cmds.submit(); sprite_stack.length = 0; - var cmds = render._main.acquire_cmd_buffer(); + cmds = render._main.acquire_cmd_buffer(); + var pass = cmds.render_pass(); try{ pass.bind_pipeline(base_pipeline.gpu); - pass.bind_model(spritemesh); - pass.bind_mat({diffuse:myimg}); + bind_model(pass,spritemesh,base_pipeline); + bind_mat(pass,{diffuse:myimg}, base_pipeline); cmds.camera(prosperon.camera.transform); pass.draw(spritemesh.count,1,0,0,0); prosperon.camera.transform.pos = campos; @@ -328,12 +409,14 @@ try{ prosperon.camera.far = 100000; cmds.camera_perspective(prosperon.camera); pass.bind_pipeline(pipeline_model.gpu); - pass.bind_model(ducky); - pass.draw(ducky.count,1,0,0,0); - cmds.camera(prosperon.camera.transform, true); - pass.bind_pipeline(base_pipeline.gpu); - pass.draw(spritemesh.count,1,0,0,0); -} catch(e) { console.log(e); } finally { + bind_model(pass,ducky.mesh,pipeline_model); + bind_mat(pass,ducky.material,pipeline_model); + pass.draw(ducky.mesh.count,1,0,0,0); + +// cmds.camera(prosperon.camera.transform, true); +// pass.bind_pipeline(base_pipeline.gpu); +// pass.draw(spritemesh.count,1,0,0,0); +} catch(e) { console.error(e); } finally { pass.end(); cmds.submit(); } @@ -344,24 +427,6 @@ var ducky; var pipeline_model; render.init = function () { - io.mount("core"); - render._main.present = gpupresent; - ducky = os.model_buffer("Duck.glb"); - upload_model(ducky); - for (var i in ducky) console.log(`ducky has ${i}`) - console.log(ducky.count) - var sprite_vert = make_shader("sprite.vert", true,0,0,0,1); - var sprite_frag = make_shader("sprite.frag", false,1,0,0,0); - base_pipeline.vertex = sprite_vert; - base_pipeline.fragment = sprite_frag; - base_pipeline.gpu = render._main.make_pipeline(base_pipeline); - var model_vert = make_shader("model.vert", true, 0,0,0,1); - var model_frag = make_shader("model.frag", false, 0,0,0,0); - pipeline_model = Object.create(base_pipeline); - pipeline_model.vertex = model_vert; - pipeline_model.fragment = model_frag; - pipeline_model.gpu = render._main.make_pipeline(pipeline_model); - std_sampler = render._main.make_sampler({ min_filter: "nearest", mag_filter: "nearest", @@ -371,6 +436,39 @@ render.init = function () { address_mode_w: "clamp_edge" }); + io.mount("core"); + render._main.present = gpupresent; + ducky = os.model_buffer("Duck.glb"); + for (var mesh of ducky) { + var mat = mesh.material; + for (var i in mat) { + var img = {}; + img.surface = os.make_texture(mat[i]); + img.texture = render._main.load_texture(img.surface); + img.texture.mode(0); + img.sampler = std_sampler; + mat[i] = img; + } + } + + ducky = ducky[0]; + + var cmds = render._main.acquire_cmd_buffer(); + upload_model(cmds,ducky.mesh); + cmds.submit(); + var sprite_vert = make_shader("sprite.vert"); + var sprite_frag = make_shader("sprite.frag"); + base_pipeline.vertex = sprite_vert; + base_pipeline.fragment = sprite_frag; + base_pipeline.gpu = make_pipeline(base_pipeline); + + var model_vert = make_shader("model.vert"); + var model_frag = make_shader("model.frag"); + pipeline_model = Object.create(base_pipeline); + pipeline_model.vertex = model_vert; + pipeline_model.fragment = model_frag; + pipeline_model.gpu = make_pipeline(pipeline_model); + /* os.make_circle2d().draw = function () { render.circle(this.body().transform().pos, this.radius, [1, 1, 0, 1]); }; diff --git a/shaders/common.hlsl b/shaders/common.hlsl deleted file mode 100644 index 80fd0655..00000000 --- a/shaders/common.hlsl +++ /dev/null @@ -1,15 +0,0 @@ -// Constant Buffer for Transformation Matrices -cbuffer TransformBuffer : register(b0, space1) -{ - float4x4 vp; - float4x4 view; - float time; // time in seconds since app start -} - -struct VSInput -{ - float3 pos : POSITION; - float2 uv : TEXCOORD0; - float4 color : COLOR0; - float3 normal : NORMAL; -}; \ No newline at end of file diff --git a/shaders/common/common.hlsl b/shaders/common/common.hlsl new file mode 100644 index 00000000..83bde469 --- /dev/null +++ b/shaders/common/common.hlsl @@ -0,0 +1,16 @@ +// Constant Buffer for Transformation Matrices +cbuffer TransformBuffer : register(b0, space1) +{ + float4x4 world_to_projection; + float4x4 projection_to_world; + float4x4 world_to_view; + float4x4 view_to_projection; + float3 camera_pos_world; + float viewport_min_z; + float3 camera_dir_world; + float viewport_max_z; + float2 viewport_size; + float2 viewport_offset; + float2 render_size; + float time; // time in seconds since app start +} diff --git a/shaders/common/model_pixel.hlsl b/shaders/common/model_pixel.hlsl new file mode 100644 index 00000000..0fbee7e0 --- /dev/null +++ b/shaders/common/model_pixel.hlsl @@ -0,0 +1,10 @@ + +struct PSInput +{ + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + float4 color : COLOR0; + float3 normal : NORMAL; + float3 gouraud : COLOR1; +}; + diff --git a/shaders/common/model_vertex.hlsl b/shaders/common/model_vertex.hlsl new file mode 100644 index 00000000..3767d19d --- /dev/null +++ b/shaders/common/model_vertex.hlsl @@ -0,0 +1,42 @@ +#include "common.hlsl" + +struct input +{ + float3 pos : pos; + float2 uv : uv; + float4 color : color; + float3 normal : norm; +}; + +struct output +{ + float4 pos : SV_POSITION; + float2 uv: TEXCOORD0; + float4 color : COLOR0; + float3 normal : NORMAL; + float3 gouraud : COLOR1; +}; + +float3 ps1_directional_light(float3 normal, float3 direction, float3 ambient, float3 lightcolor) +{ + float NdotL = dot(normal, normalize(-direction)); + NdotL = max(NdotL, 0.0f); + float3 color = ambient + lightcolor*NdotL; + return color; +} + +output main(input i) +{ + output o; + o.pos = mul(world_to_projection, float4(i.pos,1.0)); + o.uv = i.uv; + o.color = i.color; + o.normal = i.normal; + + float3 ambient = float3(0.2,0.2,0.2); + float3 lightdir = normalize(float3(1,1,1)); + o.gouraud = ps1_directional_light(i.normal, lightdir, ambient, float3(1,1,1)); + + + return o; +} diff --git a/shaders/common/pixel.hlsl b/shaders/common/pixel.hlsl new file mode 100644 index 00000000..2c6c3aee --- /dev/null +++ b/shaders/common/pixel.hlsl @@ -0,0 +1,8 @@ +#include "common.hlsl" + +// Structure for pixel shader input (from vertex shader output). +struct PSInput +{ + float2 uv : TEXCOORD0; + float4 color : COLOR0; +}; diff --git a/shaders/common/vertex.hlsl b/shaders/common/vertex.hlsl new file mode 100644 index 00000000..557b894b --- /dev/null +++ b/shaders/common/vertex.hlsl @@ -0,0 +1,25 @@ +#include "common.hlsl" + +struct input +{ + float2 pos : pos; // given as world + float2 uv : uv; // always a quad + float4 color : color; +}; + +// Output structure from the vertex shader to the pixel shader +struct output +{ + float4 pos : SV_Position; // Clip-space position + float2 uv : TEXCOORD0; // Texture coordinates + float4 color : COLOR0; // Interpolated vertex color +}; + +output main(input i) +{ + output o; + o.pos = mul(world_to_projection, float4(i.pos,0,1)); + o.uv = i.uv; + o.color = i.color; + return o; +} \ No newline at end of file diff --git a/shaders/compile.sh b/shaders/compile.sh index e687f396..bcf29585 100755 --- a/shaders/compile.sh +++ b/shaders/compile.sh @@ -1,27 +1,46 @@ -# Requires shadercross CLI installed from SDL_shadercross +#!/usr/bin/env bash + +# Ensure directories exist +mkdir -p spirv +mkdir -p msl +mkdir -p dxil +mkdir -p reflection + +# Vertex shaders for filename in *.vert.hlsl; do if [ -f "$filename" ]; then echo "compiling ${filename}" shadercross "$filename" -o "spirv/${filename/.hlsl/.spv}" shadercross "$filename" -o "msl/${filename/.hlsl/.msl}" shadercross "$filename" -o "dxil/${filename/.hlsl/.dxil}" + + # Generate reflection JSON + spirv-cross "spirv/${filename/.hlsl/.spv}" --reflect > "reflection/${filename/.hlsl/.json}" fi done +# Fragment shaders for filename in *.frag.hlsl; do if [ -f "$filename" ]; then echo "compiling ${filename}" shadercross "$filename" -o "spirv/${filename/.hlsl/.spv}" shadercross "$filename" -o "msl/${filename/.hlsl/.msl}" shadercross "$filename" -o "dxil/${filename/.hlsl/.dxil}" + + # Generate reflection JSON + spirv-cross "spirv/${filename/.hlsl/.spv}" --reflect > "reflection/${filename/.hlsl/.json}" fi done +# Compute shaders for filename in *.comp.hlsl; do if [ -f "$filename" ]; then echo "compiling ${filename}" shadercross "$filename" -o "spirv/${filename/.hlsl/.spv}" shadercross "$filename" -o "msl/${filename/.hlsl/.msl}" shadercross "$filename" -o "dxil/${filename/.hlsl/.dxil}" + + # Generate reflection JSON + spirv-cross "spirv/${filename/.hlsl/.spv}" --reflect > "reflection/${filename/.hlsl/.json}" fi done diff --git a/shaders/model.frag.hlsl b/shaders/model.frag.hlsl index 84ab1384..faa13734 100644 --- a/shaders/model.frag.hlsl +++ b/shaders/model.frag.hlsl @@ -1,15 +1,12 @@ // If using a texture instead, define: +#include "common/model_pixel.hlsl" Texture2D diffuse : register(t0, space2); SamplerState smp : register(s0, space2); -struct PSInput -{ - float4 pos : SV_POSITION; - float2 uv : TEXCOORD0; - float3 normal : NORMAL; -}; float4 main(PSInput input) : SV_TARGET { - return float4(input.uv,1,1); + float4 color = diffuse.Sample(smp, input.uv); + + return float4(color.rgb*input.gouraud, 1.0); } diff --git a/shaders/model.vert.hlsl b/shaders/model.vert.hlsl index 396f5d4f..328a9274 100644 --- a/shaders/model.vert.hlsl +++ b/shaders/model.vert.hlsl @@ -1,17 +1,6 @@ -#include "common.hlsl" +#include "common/model_vertex.hlsl" -struct VSOutput +output vertex(output o) { - float4 pos : SV_POSITION; - float2 uv : TEXCOORD0; - float3 normal : NORMAL; // Might be unused in no-light case, but we'll pass it anyway. -}; - -VSOutput main(VSInput input) -{ - VSOutput output; - output.pos = mul(vp, float4(input.pos, 1.0)); - output.uv = input.uv; - output.normal = input.normal; // World space normal if needed - return output; + return o; } diff --git a/shaders/sprite.frag.hlsl b/shaders/sprite.frag.hlsl index d60d5269..0d32a7a8 100644 --- a/shaders/sprite.frag.hlsl +++ b/shaders/sprite.frag.hlsl @@ -1,21 +1,12 @@ +#include "common/pixel.hlsl" + Texture2D diffuse : register(t0, space2); SamplerState smp : register(s0, space2); -// Structure for pixel shader input (from vertex shader output). -struct PSInput -{ - float2 uv : TEXCOORD0; - float4 color : COLOR0; -}; - // Pixel shader main function float4 main(PSInput input) : SV_TARGET { float4 color = diffuse.Sample(smp, input.uv); color *= input.color; - - if (color.a == 0.0f) - clip(-1); - return color; } \ No newline at end of file diff --git a/shaders/sprite.vert.hlsl b/shaders/sprite.vert.hlsl index 97acf26c..931ffbf1 100644 --- a/shaders/sprite.vert.hlsl +++ b/shaders/sprite.vert.hlsl @@ -1,21 +1,8 @@ -#include "common.hlsl" - -// Output structure from the vertex shader to the pixel shader -struct VertexOutput -{ - float4 pos : SV_Position; // Clip-space position - float2 uv : TEXCOORD0; // Texture coordinates - float4 color : COLOR0; // Interpolated vertex color -}; +#include "common/vertex.hlsl" // Vertex shader -VertexOutput main(VSInput input) +output vertex(output i) { - VertexOutput output; - output.pos = mul(vp, float4(input.pos,1.0)); - output.uv = input.uv; - output.color = input.color; - - return output; + return i; } diff --git a/source/jsffi.c b/source/jsffi.c index 4a2e6b93..fd7da9dd 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -134,18 +134,22 @@ typedef struct texture_vertex { uint8_t r, g, b,a; } texture_vertex; -typedef struct app_data { - HMM_Mat4 vp; - HMM_Mat4 view; - float time; -} app_data; - -typedef struct { - HMM_Mat4 camera_matrix; - size_t start_vert; - size_t start_indx; -} render_batch; -static render_batch *batches = NULL; +#pragma pack(push, 1) +typedef struct shader_globals { + HMM_Mat4 world_to_projection; + HMM_Mat4 projection_to_world; + HMM_Mat4 world_to_view; + HMM_Mat4 view_to_projection; + HMM_Vec3 camera_pos_world; + HMM_Vec3 camera_dir_world; + float viewport_min_z; + float viewport_max_z; + HMM_Vec2 viewport_size; + HMM_Vec2 viewport_offset; + HMM_Vec2 render_size; + float time; +} shader_globals; +#pragma pack(pop) static inline size_t typed_array_bytes(JSTypedArrayEnum type) { switch(type) { @@ -250,7 +254,7 @@ void free_gpu_buffer(JSRuntime *rt, void *opaque, void *ptr) free(ptr); } -JSValue make_gpu_buffer(JSContext *js, void *data, size_t size, int type, int elements, int copy) +JSValue make_gpu_buffer(JSContext *js, void *data, size_t size, int type, int elements, int copy, int index) { JSValue tstack[3]; tstack[1] = JS_UNDEFINED; @@ -261,6 +265,7 @@ JSValue make_gpu_buffer(JSContext *js, void *data, size_t size, int type, int el tstack[0] = JS_NewArrayBuffer(js,data,size,free_gpu_buffer, NULL, 0); JSValue ret = JS_NewTypedArray(js, 3, tstack, type); JS_SetPropertyStr(js,ret,"stride", number2js(js,typed_array_bytes(type)*elements)); + JS_SetPropertyStr(js,ret,"index", JS_NewBool(js,index)); JS_FreeValue(js,tstack[0]); return ret; } @@ -271,11 +276,50 @@ void *get_gpu_buffer(JSContext *js, JSValue argv, size_t *stride, size_t *size) JSValue buf = JS_GetTypedArrayBuffer(js, argv, &o, &len, &bytes); void *data = JS_GetArrayBuffer(js, &msize, buf); JS_FreeValue(js,buf); - *stride = js_getnum_str(js, argv, "stride"); + if (stride) *stride = js_getnum_str(js, argv, "stride"); if (size) *size = msize; return data; } +typedef struct { + JSValue val; + void *ptr; + size_t size; + int need_new; +} BufferCheckResult; + +static BufferCheckResult get_or_extend_buffer( + JSContext *js, + JSValue old_mesh, + JSAtom prop, + size_t needed_size, + int type, + int elements_per_item, + int copy, + int index +) { + BufferCheckResult res = { JS_UNDEFINED, NULL, 0, 0 }; + if (!JS_IsUndefined(old_mesh)) { + JSValue old_buf = JS_GetProperty(js, old_mesh, prop); + if (!JS_IsUndefined(old_buf)) { + size_t old_size; + void *data = get_gpu_buffer(js, old_buf, NULL, &old_size); + if (data && old_size >= needed_size) { + // Old buffer is large enough + res.val = old_buf; // keep it + res.ptr = data; + res.size = old_size; + return res; + } + JS_FreeValue(js, old_buf); + } + } + // If we reach here, we need a new buffer + res.need_new = 1; + printf("NEED NEW BUFFER\n"); + return res; +} + #ifndef _WIN32 #include #endif @@ -294,6 +338,8 @@ struct lrtb { float b; }; +static SDL_GPUDevice *global_gpu; + typedef struct lrtb lrtb; lrtb js2lrtb(JSContext *js, JSValue v) @@ -368,12 +414,12 @@ void SDL_Thread_free(JSRuntime *rt, SDL_Thread *t) { } -void SDL_GPUComputePass_free(JSRuntime *rt, SDL_GPUComputePass *c) { SDL_EndGPUComputePass(c); } +void SDL_GPUComputePass_free(JSRuntime *rt, SDL_GPUComputePass *c) { } void SDL_GPUCopyPass_free(JSRuntime *rt, SDL_GPUCopyPass *c) { } void SDL_GPURenderPass_free(JSRuntime *rt, SDL_GPURenderPass *c) { } #define GPURELEASECLASS(NAME) \ -void SDL_GPU##NAME##_free(JSRuntime *rt, SDL_GPU##NAME *c) { printf("IMPLEMENT %s FREE\n", #NAME); } \ +void SDL_GPU##NAME##_free(JSRuntime *rt, SDL_GPU##NAME *c) { printf("releasing...\n"); SDL_ReleaseGPU##NAME(global_gpu, c); } \ QJSCLASS(SDL_GPU##NAME) \ QJSCLASS(transform) @@ -404,6 +450,7 @@ QJSCLASS(SDL_Surface, JS_SetProperty(js, j, width_atom, number2js(js,n->w)); JS_SetProperty(js,j,height_atom,number2js(js,n->h)); ) + QJSCLASS(SDL_GPUDevice) QJSCLASS(SDL_Thread) @@ -413,12 +460,13 @@ GPURELEASECLASS(GraphicsPipeline) GPURELEASECLASS(Sampler) GPURELEASECLASS(Shader) GPURELEASECLASS(Texture) +GPURELEASECLASS(TransferBuffer) +GPURELEASECLASS(Fence) QJSCLASS(SDL_GPUCommandBuffer) QJSCLASS(SDL_GPUComputePass) QJSCLASS(SDL_GPUCopyPass) QJSCLASS(SDL_GPURenderPass) - QJSCLASS(SDL_Cursor) int js_arrlen(JSContext *js,JSValue v) { @@ -440,6 +488,33 @@ static inline HMM_Mat3 js2transform_mat3(JSContext *js, JSValue v) return transform2mat3(T); } +void *gpu_buffer_unpack(JSContext *js, SDL_GPUDevice *device, JSValue buffer, size_t *size, void **send_data, SDL_GPUBuffer **send_gpu) +{ + size_t o, len, bytes, msize; + JSValue buf = JS_GetTypedArrayBuffer(js, buffer, &o, &len, &bytes); + void *data = JS_GetArrayBuffer(js, &msize, buf); + JS_FreeValue(js,buf); + if (size) *size = msize; + if (send_gpu) { + JSValue idx = JS_GetPropertyStr(js,buffer, "index"); + Uint32 usage = JS_ToBool(js,idx) ? SDL_GPU_BUFFERUSAGE_INDEX : SDL_GPU_BUFFERUSAGE_VERTEX; + JS_FreeValue(js,idx); + JSValue gpu = JS_GetPropertyStr(js,buffer,"gpu"); + *send_gpu = js2SDL_GPUBuffer(js,gpu); + if (!*send_gpu) { + *send_gpu = SDL_CreateGPUBuffer(device, &(SDL_GPUBufferCreateInfo) { .usage=usage,.size=*size}); + if (!*send_gpu) printf("COULDN'T MAKE GPU BUFFER: %s\n", SDL_GetError()); + JS_SetPropertyStr(js, buffer, "gpu", SDL_GPUBuffer2js(js,*send_gpu)); + } + } + + if (send_data) + *send_data = data; + return data; +} + + + void *get_typed_buffer(JSContext *js, JSValue argv, size_t *len) { size_t o,bytes,size; @@ -873,17 +948,17 @@ JSValue make_quad_indices_buffer(JSContext *js, int quads) uint16_t *indices = malloc(sizeof(*indices)*count); for (int i = 0, v = 0; v < verts; i +=6, v += 4) { indices[i] = v; - indices[i+1] = v+1; - indices[i+2] = v+2; + indices[i+1] = v+2; + indices[i+2] = v+1; indices[i+3] = v+2; - indices[i+4] = v+1; - indices[i+5] = v+3; + indices[i+4] = v+3; + indices[i+5] = v+1; } if (!JS_IsUndefined(idx_buffer)) JS_FreeValue(js,idx_buffer); - idx_buffer = make_gpu_buffer(js,indices, sizeof(*indices)*count, JS_TYPED_ARRAY_UINT16, 1,0); + idx_buffer = make_gpu_buffer(js,indices, sizeof(*indices)*count, JS_TYPED_ARRAY_UINT16, 1,0,1); return JS_DupValue(js,idx_buffer); } @@ -911,25 +986,9 @@ JSC_CCALL(os_make_text_buffer, arrfree(buffer); - JSValue jspos = make_gpu_buffer(js, pos, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0); - JSValue jsuv = make_gpu_buffer(js, uv, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0); - JSValue jscolor = make_gpu_buffer(js, color, sizeof(HMM_Vec4)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 4,0); - -/* - JSValue jsbuffer = JS_NewArrayBuffer(js,buffer,sizeof(*buffer)*arrlen(buffer), free_gpu_buffer, NULL, 0); - JSValue tstack[3]; - tstack[0] = jsbuffer; - tstack[1] = JS_UNDEFINED;//number2js(js,0); - tstack[2] = JS_UNDEFINED; - JSValue jspos = JS_NewTypedArray(js, 3, tstack, JS_TYPED_ARRAY_FLOAT32); - JS_SetPropertyStr(js,jspos, "stride", number2js(js,sizeof(*buffer))); - tstack[1] = number2js(js,8); - JSValue jsuv = JS_NewTypedArray(js,3,tstack,JS_TYPED_ARRAY_FLOAT32); - JS_SetPropertyStr(js,jsuv, "stride", number2js(js,sizeof(*buffer))); - tstack[1] = number2js(js,16); - JSValue jscolor = JS_NewTypedArray(js,3,tstack,JS_TYPED_ARRAY_FLOAT32); - JS_SetPropertyStr(js,jscolor, "stride", number2js(js,sizeof(*buffer))); -*/ + JSValue jspos = make_gpu_buffer(js, pos, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0,0); + JSValue jsuv = make_gpu_buffer(js, uv, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0,0); + JSValue jscolor = make_gpu_buffer(js, color, sizeof(HMM_Vec4)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 4,0,0); size_t quads = verts/4; size_t count = verts/2*3; @@ -1941,7 +2000,8 @@ JSC_SCALL(SDL_Window_make_renderer, JSC_SCALL(SDL_Window_make_gpu, SDL_Window *win = js2SDL_Window(js,self); - SDL_GPUDevice *gpu = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_MSL, 1, NULL); + SDL_GPUDevice *gpu = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_MSL, 0, NULL); + global_gpu = gpu; return SDL_GPUDevice2js(js,gpu); ) @@ -2533,7 +2593,7 @@ JSC_CCALL(gpu_load_gltf_model, // Create a JS buffer for positions right now // Positions remain float32. JS_SetProperty(js, ret, pos_atom, - make_gpu_buffer(js, positions, sizeof(float)*3*vertex_count, JS_TYPED_ARRAY_FLOAT32, 3, 0)); + make_gpu_buffer(js, positions, sizeof(float)*3*vertex_count, JS_TYPED_ARRAY_FLOAT32, 3, 0,0)); break; } @@ -2558,7 +2618,7 @@ JSC_CCALL(gpu_load_gltf_model, // Immediately store normals JS_SetProperty(js, ret, norm_atom, - make_gpu_buffer(js, normals_packed, sizeof(int16_t)*3*accessor->count, JS_TYPED_ARRAY_INT16, 3, 1 /* normalized */)); + make_gpu_buffer(js, normals_packed, sizeof(int16_t)*3*accessor->count, JS_TYPED_ARRAY_INT16, 3, 1 /* normalized */,0)); break; } case cgltf_attribute_type_texcoord: { @@ -2578,7 +2638,7 @@ JSC_CCALL(gpu_load_gltf_model, // Immediately store UVs JS_SetProperty(js, ret, uv_atom, - make_gpu_buffer(js, uvs_packed, sizeof(uint16_t)*2*accessor->count, JS_TYPED_ARRAY_UINT16, 2, 1 /* normalized */)); + make_gpu_buffer(js, uvs_packed, sizeof(uint16_t)*2*accessor->count, JS_TYPED_ARRAY_UINT16, 2, 1 /* normalized */,0)); } break; } @@ -2606,7 +2666,7 @@ JSC_CCALL(gpu_load_gltf_model, generate_normals(positions, vertex_count, indices_data, index_count, normals_packed); JS_SetProperty(js, ret, norm_atom, - make_gpu_buffer(js, normals_packed, sizeof(int16_t)*3*vertex_count, JS_TYPED_ARRAY_INT16, 3, 1 /* normalized */)); + make_gpu_buffer(js, normals_packed, sizeof(int16_t)*3*vertex_count, JS_TYPED_ARRAY_INT16, 3, 1 /* normalized */,0)); } else { // Non-indexed normal generation would go here if needed. // For simplicity, just create default normals (0,0,1) @@ -2617,14 +2677,14 @@ JSC_CCALL(gpu_load_gltf_model, normals_packed[i*3+2] = 32767; // (0,0,1) } JS_SetProperty(js, ret, norm_atom, - make_gpu_buffer(js, normals_packed, sizeof(int16_t)*3*vertex_count, JS_TYPED_ARRAY_INT16, 3, 1)); + make_gpu_buffer(js, normals_packed, sizeof(int16_t)*3*vertex_count, JS_TYPED_ARRAY_INT16, 3, 1,0)); } } // Store indices if we have them if (indices_data && index_count > 0) { JS_SetProperty(js, ret, indices_atom, - make_gpu_buffer(js, indices_data, sizeof(uint16_t)*index_count, JS_TYPED_ARRAY_UINT16, 1, 0)); + make_gpu_buffer(js, indices_data, sizeof(uint16_t)*index_count, JS_TYPED_ARRAY_UINT16, 1, 0,1)); } // Store vertices and count @@ -2660,7 +2720,7 @@ JSC_CCALL(renderer_make_sprite_mesh, HMM_Vec2 *posdata = malloc(sizeof(*posdata)*verts); HMM_Vec2 *uvdata = malloc(sizeof(*uvdata)*verts); - HMM_Vec4 *colordata = malloc(sizeof(*colordata)*quads); + HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts); for (int i = 0; i < quads; i++) { JSValue sub = JS_GetPropertyUint32(js,sprites,i); @@ -2701,7 +2761,10 @@ JSC_CCALL(renderer_make_sprite_mesh, uvdata[base + 2] = (HMM_Vec2){ src.x, src.y }; uvdata[base + 3] = (HMM_Vec2){ src.x + src.w, src.y }; - colordata[i] = color; + colordata[base] = color; + colordata[base+1] = color; + colordata[base+2] = color; + colordata[base+3] = color; JS_FreeValue(js,sub); JS_FreeValue(js,jscolor); @@ -2709,9 +2772,9 @@ JSC_CCALL(renderer_make_sprite_mesh, } ret = JS_NewObject(js); - JS_SetProperty(js, ret, pos_atom, make_gpu_buffer(js, posdata, sizeof(*posdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0)); - JS_SetProperty(js, ret, uv_atom, make_gpu_buffer(js, uvdata, sizeof(*uvdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0)); - JS_SetProperty(js, ret, color_atom, make_gpu_buffer(js, colordata, sizeof(*colordata) * quads, JS_TYPED_ARRAY_FLOAT32, 0, 0)); + JS_SetProperty(js, ret, pos_atom, make_gpu_buffer(js, posdata, sizeof(*posdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0,0)); + JS_SetProperty(js, ret, uv_atom, make_gpu_buffer(js, uvdata, sizeof(*uvdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0,0)); + JS_SetProperty(js, ret, color_atom, make_gpu_buffer(js, colordata, sizeof(*colordata) * verts, JS_TYPED_ARRAY_FLOAT32, 0, 0,0)); JS_SetProperty(js, ret, indices_atom, make_quad_indices_buffer(js, quads)); JS_SetProperty(js, ret, vertices_atom, number2js(js, verts)); JS_SetProperty(js, ret, count_atom, number2js(js, count)); @@ -2751,6 +2814,15 @@ JSC_CCALL(gpu_claim_window, SDL_GPUDevice *d = js2SDL_GPUDevice(js,self); SDL_Window *w = js2SDL_Window(js, argv[0]); SDL_ClaimWindowForGPUDevice(d,w); + if (!SDL_SetGPUSwapchainParameters(d,w,SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_MAILBOX)) + printf("Could not set: %s\n", SDL_GetError()); +// SDL_SetGPUAllowedFramesInFlight(d, 1); +) + +JSC_CCALL(gpu_set_swapchain, + SDL_GPUDevice *d = js2SDL_GPUDevice(js,self); + SDL_Window *w = js2SDL_Window(js,argv[0]); + ) JSC_CCALL(gpu_load_texture, @@ -2984,8 +3056,6 @@ JSC_CCALL(gpu_logical_size, ) -static SDL_GPUVertexInputState state_2d; - int atom2front_face(JSAtom atom) { if(atom == cw_atom) return SDL_GPU_FRONTFACE_CLOCKWISE; @@ -3072,80 +3142,152 @@ static JSValue js_gpu_make_pipeline(JSContext *js, JSValueConst self, int argc, SDL_GPUGraphicsPipelineCreateInfo info = {0}; + // --------------------------------------------------- + // 2. Retrieve vertex buffer descriptions array + // --------------------------------------------------- + JSValue vbd_val = JS_GetPropertyStr(js, pipe, "vertex_buffer_descriptions"); + Uint32 vbd_len = js_arrlen(js,vbd_val); + SDL_GPUVertexBufferDescription vbd[vbd_len]; + for (Uint32 i = 0; i < vbd_len; i++) { + JSValue elem = JS_GetPropertyUint32(js, vbd_val, i); + if (JS_IsObject(elem)) { + JSValue slot_val = JS_GetPropertyStr(js, elem, "slot"); + JSValue pitch_val = JS_GetPropertyStr(js, elem, "pitch"); + JSValue rate_val = JS_GetPropertyStr(js, elem, "input_rate"); + JSValue step_val = JS_GetPropertyStr(js, elem, "instance_step_rate"); + + // Slot + Uint32 slot = 0; + JS_ToUint32(js, &slot, slot_val); + JS_FreeValue(js, slot_val); + + // Pitch + Uint32 pitch = 0; + JS_ToUint32(js, &pitch, pitch_val); + JS_FreeValue(js, pitch_val); + + // Input Rate + const char *rate_str = JS_ToCString(js, rate_val); + SDL_GPUVertexInputRate input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX; + if (rate_str) { + if (!strcmp(rate_str, "vertex")) input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX; + else if (!strcmp(rate_str, "instance")) input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE; + JS_FreeCString(js, rate_str); + } + JS_FreeValue(js, rate_val); + + // Instance Step Rate + Uint32 step_rate = 0; + JS_ToUint32(js, &step_rate, step_val); + JS_FreeValue(js, step_val); + + vbd[i].slot = slot; + vbd[i].pitch = pitch; + vbd[i].input_rate = input_rate; + vbd[i].instance_step_rate = step_rate; + } + JS_FreeValue(js, elem); + } + JS_FreeValue(js, vbd_val); + + // --------------------------------------------------- + // 3. Retrieve vertex attributes array + // --------------------------------------------------- + JSValue vat_val = JS_GetPropertyStr(js, pipe, "vertex_attributes"); + Uint32 vat_len = js_arrlen(js,vat_val); + + SDL_GPUVertexAttribute vat[vat_len]; + for (Uint32 i = 0; i < vat_len; i++) { + JSValue elem = JS_GetPropertyUint32(js, vat_val, i); + if (JS_IsObject(elem)) { + JSValue loc_val = JS_GetPropertyStr(js, elem, "location"); + JSValue slot_val = JS_GetPropertyStr(js, elem, "buffer_slot"); + JSValue fmt_val = JS_GetPropertyStr(js, elem, "format"); + JSValue off_val = JS_GetPropertyStr(js, elem, "offset"); + + // location + Uint32 location = 0; + JS_ToUint32(js, &location, loc_val); + JS_FreeValue(js, loc_val); + + // buffer_slot + Uint32 buffer_slot = 0; + JS_ToUint32(js, &buffer_slot, slot_val); + JS_FreeValue(js, slot_val); + + // format + const char *fmt_str = JS_ToCString(js, fmt_val); + SDL_GPUVertexElementFormat format; + if (fmt_str) { + // Map from string to SDL_GPUVertexElementFormat + if (!strcmp(fmt_str, "float2")) format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2; + else if (!strcmp(fmt_str, "float3")) format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3; + else if (!strcmp(fmt_str, "float4")) format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4; + else if (!strcmp(fmt_str, "color")) + // Engine handles color as 4 packed bytes +// format = SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4_NORM; + format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4; + // else fallback to FLOAT1 + + JS_FreeCString(js, fmt_str); + } + JS_FreeValue(js, fmt_val); + + // offset + Uint32 offset = 0; + JS_ToUint32(js, &offset, off_val); + JS_FreeValue(js, off_val); + + vat[i].location = location; + vat[i].buffer_slot = buffer_slot; + vat[i].format = format; + vat[i].offset = offset; + } + JS_FreeValue(js, elem); + } + JS_FreeValue(js, vat_val); + info.vertex_input_state = (SDL_GPUVertexInputState){ - .num_vertex_buffers = 4, - .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]){{ - .slot = 0, - .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX, - .instance_step_rate = 0, - .pitch = sizeof(float)*3 - }, { - .slot = 1, - .pitch = sizeof(float)*2, - .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX, - .instance_step_rate = 0 - }, - { - .slot = 2, - .pitch = sizeof(float)*4, - .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX, - .instance_step_rate=0 - }, - { - .slot = 3, - .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX, - .instance_step_rate = 0, - .pitch = sizeof(float)*3 - }}, - .num_vertex_attributes = 4, - .vertex_attributes = (SDL_GPUVertexAttribute[]){{ - .buffer_slot = 0, - .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, - .location = 0, - .offset = 0 - }, { - .buffer_slot = 1, - .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, - .location = 1, - .offset = 0, - }, { - .buffer_slot = 2, - .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4, - .location = 2, - .offset = 0 - }, { - .buffer_slot = 3, - .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, - .location = 3, - .offset = 0 - } - } - }; + .vertex_buffer_descriptions = vbd, + .num_vertex_buffers = vbd_len, + .vertex_attributes = vat, + .num_vertex_attributes = vat_len + }; - - // Vertex and Fragment Shaders + // Vertex Shader JSValue vertex_val = JS_GetPropertyStr(js, pipe, "vertex"); if (JS_IsUndefined(vertex_val)) { JS_FreeValue(js, vertex_val); - return JS_ThrowTypeError(js, "pipeline object must have a 'vertex' shader"); + return JS_ThrowTypeError(js, "pipeline object must have a 'vertex' property"); } - - info.vertex_shader = js2SDL_GPUShader(js, vertex_val); + + JSValue vertex_gpu_val = JS_GetPropertyStr(js, vertex_val, "gpu"); + if (JS_IsUndefined(vertex_gpu_val)) { + JS_FreeValue(js, vertex_gpu_val); + JS_FreeValue(js, vertex_val); + return JS_ThrowTypeError(js, "pipeline.vertex must have a 'gpu' property"); + } + info.vertex_shader = js2SDL_GPUShader(js, vertex_gpu_val); + JS_FreeValue(js, vertex_gpu_val); JS_FreeValue(js, vertex_val); + // Fragment Shader JSValue fragment_val = JS_GetPropertyStr(js, pipe, "fragment"); if (JS_IsUndefined(fragment_val)) { JS_FreeValue(js, fragment_val); - return JS_ThrowTypeError(js, "pipeline object must have a 'fragment' shader"); + return JS_ThrowTypeError(js, "pipeline object must have a 'fragment' property"); } - info.fragment_shader = js2SDL_GPUShader(js, fragment_val); + + JSValue fragment_gpu_val = JS_GetPropertyStr(js, fragment_val, "gpu"); + if (JS_IsUndefined(fragment_gpu_val)) { + JS_FreeValue(js, fragment_gpu_val); + JS_FreeValue(js, fragment_val); + return JS_ThrowTypeError(js, "pipeline.fragment must have a 'gpu' property"); + } + info.fragment_shader = js2SDL_GPUShader(js, fragment_gpu_val); + JS_FreeValue(js, fragment_gpu_val); JS_FreeValue(js, fragment_val); - if (info.vertex_shader == NULL || info.fragment_shader == NULL) return JS_ThrowInternalError(js, "Failed to retrieve shaders"); - - // Vertex Input State -// info.vertex_input_state = state2d; // Assuming state2d is predefined - // Primitive Type JSValue primitive_val = JS_GetPropertyStr(js, pipe, "primitive"); JSAtom primitive_atom = JS_ValueToAtom(js, primitive_val); @@ -3173,35 +3315,6 @@ static JSValue js_gpu_make_pipeline(JSContext *js, JSValueConst self, int argc, JS_FreeAtom(js, face_atom); JS_FreeValue(js, face_val); - // Depth Bias (assuming defaults if not specified) - JSValue depth_bias_val = JS_GetPropertyStr(js, pipe, "depth_bias"); - if (!JS_IsUndefined(depth_bias_val)) JS_ToFloat64(js, &info.rasterizer_state.depth_bias_constant_factor, depth_bias_val); - JS_FreeValue(js, depth_bias_val); - - JSValue depth_bias_slope_scale_val = JS_GetPropertyStr(js, pipe, "depth_bias_slope_scale"); - if (!JS_IsUndefined(depth_bias_slope_scale_val)) - JS_ToFloat64(js, &info.rasterizer_state.depth_bias_slope_factor, depth_bias_slope_scale_val); - - JS_FreeValue(js, depth_bias_slope_scale_val); - - JSValue depth_bias_clamp_val = JS_GetPropertyStr(js, pipe, "depth_bias_clamp"); - if (!JS_IsUndefined(depth_bias_clamp_val)) - JS_ToFloat64(js, &info.rasterizer_state.depth_bias_clamp, depth_bias_clamp_val); - JS_FreeValue(js, depth_bias_clamp_val); - - // Enable Depth Bias (assuming false if not specified) - JSValue enable_depth_bias_val = JS_GetPropertyStr(js, pipe, "enable_depth_bias"); - info.rasterizer_state.enable_depth_bias = JS_ToBool(js, enable_depth_bias_val); - JS_FreeValue(js, enable_depth_bias_val); - - // Enable Depth Clip (assuming true if not specified) - JSValue enable_depth_clip_val = JS_GetPropertyStr(js, pipe, "enable_depth_clip"); - if (JS_IsUndefined(enable_depth_clip_val)) - info.rasterizer_state.enable_depth_clip = true; // Default - else - info.rasterizer_state.enable_depth_clip = JS_ToBool(js, enable_depth_clip_val); - JS_FreeValue(js, enable_depth_clip_val); - // Depth Stencil State JSValue depth_val = JS_GetPropertyStr(js, pipe, "depth"); if (JS_IsObject(depth_val)) { @@ -3251,7 +3364,6 @@ static JSValue js_gpu_make_pipeline(JSContext *js, JSValueConst self, int argc, if (!JS_IsUndefined(enabled_val)) info.depth_stencil_state.enable_stencil_test = JS_ToBool(js, enabled_val); JS_FreeValue(js, enabled_val); - // Front Stencil JSValue front_val = JS_GetPropertyStr(js, stencil_val, "front"); if (JS_IsObject(front_val)) { @@ -3262,7 +3374,6 @@ static JSValue js_gpu_make_pipeline(JSContext *js, JSValueConst self, int argc, JS_FreeAtom(js, compare_atom); } JS_FreeValue(js, compare_val); - JSValue fail_val = JS_GetPropertyStr(js, front_val, "fail"); if (!JS_IsUndefined(fail_val)) { JSAtom fail_atom = JS_ValueToAtom(js, fail_val); @@ -3278,7 +3389,6 @@ static JSValue js_gpu_make_pipeline(JSContext *js, JSValueConst self, int argc, JS_FreeAtom(js, depth_fail_atom); } JS_FreeValue(js, depth_fail_val); - JSValue pass_val = JS_GetPropertyStr(js, front_val, "pass"); if (!JS_IsUndefined(pass_val)) { JSAtom pass_atom = JS_ValueToAtom(js, pass_val); @@ -3288,7 +3398,6 @@ static JSValue js_gpu_make_pipeline(JSContext *js, JSValueConst self, int argc, JS_FreeValue(js, pass_val); } JS_FreeValue(js, front_val); - // Back Stencil JSValue back_val = JS_GetPropertyStr(js, stencil_val, "back"); if (JS_IsObject(back_val)) { @@ -3307,7 +3416,6 @@ static JSValue js_gpu_make_pipeline(JSContext *js, JSValueConst self, int argc, JS_FreeAtom(js, fail_atom); } JS_FreeValue(js, fail_val); - JSValue depth_fail_val = JS_GetPropertyStr(js, back_val, "depth_fail"); if (!JS_IsUndefined(depth_fail_val)) { JSAtom depth_fail_atom = JS_ValueToAtom(js, depth_fail_val); @@ -3329,14 +3437,18 @@ static JSValue js_gpu_make_pipeline(JSContext *js, JSValueConst self, int argc, // Compare Mask JSValue compare_mask_val = JS_GetPropertyStr(js, stencil_val, "compare_mask"); if (!JS_IsUndefined(compare_mask_val)) { - JS_ToUint32(js, &info.depth_stencil_state.compare_mask, compare_mask_val); + uint32_t tmp; + JS_ToUint32(js, &tmp, compare_mask_val); + info.depth_stencil_state.compare_mask = tmp; } JS_FreeValue(js, compare_mask_val); // Write Mask JSValue write_mask_val = JS_GetPropertyStr(js, stencil_val, "write_mask"); if (!JS_IsUndefined(write_mask_val)) { - JS_ToUint32(js, &info.depth_stencil_state.write_mask, write_mask_val); + uint32_t tmp; + JS_ToUint32(js, &tmp, write_mask_val); + info.depth_stencil_state.write_mask = tmp; } JS_FreeValue(js, write_mask_val); } @@ -3348,7 +3460,6 @@ static JSValue js_gpu_make_pipeline(JSContext *js, JSValueConst self, int argc, // Enable Blend JSValue enabled_val = JS_GetPropertyStr(js, blend_val, "enabled"); if (!JS_IsUndefined(enabled_val)) { - info.target_info.has_depth_stencil_target = true; // Assuming blend requires depth stencil // Depending on SDL_GPU's API, adjust accordingly // For now, we'll set blend_state fields // But since target_info is separate, we need to handle it appropriately @@ -3407,44 +3518,15 @@ static JSValue js_gpu_make_pipeline(JSContext *js, JSValueConst self, int argc, // info.blend_state.alpha_to_coverage = JS_ToBool(js, atc_val); JS_FreeValue(js, atc_val); - // Target Info - // For simplicity, assume a single color target with a default format - // You can expand this to handle multiple targets based on your needs -// SDL_GPUColorTargetDescription color_target_desc = { SDL_GPU_RGBA8 }; // Default format -// info.target_info.color_target_descriptions = &color_target_desc; - info.target_info.num_color_targets = 1; -// info.target_info.depth_stencil_format = SDL_GPU_DEPTH24_STENCIL8; // Default depth-stencil format - JSValue has_depth_stencil_target_val = JS_GetPropertyStr(js, pipe, "has_depth_stencil_target"); - if (!JS_IsUndefined(has_depth_stencil_target_val)) { - info.target_info.has_depth_stencil_target = JS_ToBool(js, has_depth_stencil_target_val); - } else { - info.target_info.has_depth_stencil_target = true; // Default - } - JS_FreeValue(js, has_depth_stencil_target_val); - - // Label (optional) - JSValue label_val = JS_GetPropertyStr(js, pipe, "label"); - if (JS_IsString(label_val)) { - const char *label_str = JS_ToCString(js, label_val); - if (label_str) { - // Assuming SDL_GPUGraphicsPipelineCreateInfo has a label field - // Modify the struct if necessary - // Example: - // strncpy(info.label, label_str, sizeof(info.label) - 1); - // info.label[sizeof(info.label) - 1] = '\0'; - // For demonstration, we'll ignore it - JS_FreeCString(js, label_str); - } - } - JS_FreeValue(js, label_val); - JSValue jswin = JS_GetPropertyStr(js,self,"window"); SDL_Window *win = js2SDL_Window(js, jswin); info.target_info =(SDL_GPUGraphicsPipelineTargetInfo) { .num_color_targets = 1, .color_target_descriptions = (SDL_GPUColorTargetDescription[]) {{ .format = SDL_GetGPUSwapchainTextureFormat(gpu, win) - }} + }}, + .has_depth_stencil_target = 1, + .depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT, }; JS_FreeValue(js,jswin); @@ -3452,8 +3534,6 @@ static JSValue js_gpu_make_pipeline(JSContext *js, JSValueConst self, int argc, SDL_GPUGraphicsPipeline *pipeline = SDL_CreateGPUGraphicsPipeline(gpu, &info); if (!pipeline) return JS_ThrowInternalError(js, "Failed to create GPU pipeline"); - printf("MADE PIPELINE\n"); - return SDL_GPUGraphicsPipeline2js(js, pipeline); } @@ -3863,12 +3943,20 @@ JSC_CCALL(gpu_viewport, ) +static HMM_Vec3 base_quad[4] = { + {0.0,0.0,1.0}, + {1.0,0.0,1.0}, + {0.0,1.0,1.0}, + {1.0,1.0,1.0} + }; + JSC_CCALL(gpu_make_sprite_mesh, size_t quads = js_arrlen(js, argv[0]); size_t verts = quads*4; size_t count = quads*6; - HMM_Vec3 *posdata = malloc(sizeof(*posdata)*verts); + // Prepare arrays on CPU + HMM_Vec2 *posdata = malloc(sizeof(*posdata)*verts); HMM_Vec2 *uvdata = malloc(sizeof(*uvdata)*verts); HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts); @@ -3878,56 +3966,111 @@ JSC_CCALL(gpu_make_sprite_mesh, transform *tr = js2transform(js,jstransform); JSValue jssrc = JS_GetProperty(js,sub,src_atom); JSValue jscolor = JS_GetProperty(js,sub,color_atom); - HMM_Vec4 color; rect src; if (JS_IsUndefined(jssrc)) - src = (rect){.x = 0, .y = 0, .w = 1, .h = 1}; + src = (rect){0,0,1,1}; else src = js2rect(js,jssrc); - + + HMM_Vec4 color; if (JS_IsUndefined(jscolor)) color = (HMM_Vec4){1,1,1,1}; else color = js2vec4(js,jscolor); - // Calculate the base index for the current quad - size_t base = i * 4; - + size_t base = i*4; HMM_Mat3 trmat = js2transform_mat3(js,jstransform); - - HMM_Vec3 base_quad[4] = { - {0.0,0.0,1.0}, - {1.0,0.0,1.0}, - {0.0,1.0,1.0}, - {1.0,1.0,1.0} - }; for (int j = 0; j < 4; j++) - posdata[base+j] = HMM_MulM3V3(trmat, base_quad[j]); + posdata[base+j] = HMM_MulM3V3(trmat, base_quad[j]).xy; - // Define the UV coordinates based on the source rectangle - uvdata[base + 0] = (HMM_Vec2){ src.x, src.y + src.h }; - uvdata[base + 1] = (HMM_Vec2){ src.x + src.w, src.y + src.h }; - uvdata[base + 2] = (HMM_Vec2){ src.x, src.y }; - uvdata[base + 3] = (HMM_Vec2){ src.x + src.w, src.y }; + uvdata[base+0] = (HMM_Vec2){src.x, src.y+src.h}; + uvdata[base+1] = (HMM_Vec2){src.x+src.w, src.y+src.h}; + uvdata[base+2] = (HMM_Vec2){src.x, src.y}; + uvdata[base+3] = (HMM_Vec2){src.x+src.w, src.y}; - colordata[4*i + 0] = color; - colordata[4*i + 1] = color; - colordata[4*i + 2] = color; - colordata[4*i + 3] = color; + colordata[base+0] = color; + colordata[base+1] = color; + colordata[base+2] = color; + colordata[base+3] = color; JS_FreeValue(js,sub); JS_FreeValue(js,jscolor); JS_FreeValue(js,jssrc); } + // Check old mesh + JSValue old_mesh = JS_UNDEFINED; + if (argc > 1) + old_mesh = argv[1]; + + // Needed sizes + size_t pos_size = sizeof(*posdata)*verts; + size_t uv_size = sizeof(*uvdata)*verts; + size_t color_size = sizeof(*colordata)*verts; + + BufferCheckResult pos_chk = get_or_extend_buffer(js, old_mesh, pos_atom, pos_size, JS_TYPED_ARRAY_FLOAT32, 2, 1, 0); + BufferCheckResult uv_chk = get_or_extend_buffer(js, old_mesh, uv_atom, uv_size, JS_TYPED_ARRAY_FLOAT32, 2, 1, 0); + BufferCheckResult color_chk = get_or_extend_buffer(js, old_mesh, color_atom, color_size, JS_TYPED_ARRAY_FLOAT32, 4, 1, 0); + + // For indices, we might have a shared global buffer like idx_buffer. If we want a per-mesh buffer, + // do the same check. If we rely on a global make_quad_indices_buffer that can handle extension, + // we can just call it. Otherwise, implement similarly: + int need_idx_new = 0; + // Suppose we have a function get_or_extend_buffer for indices as well, or we just always call + // make_quad_indices_buffer. If it returns a shared buffer that can handle it, that's fine. + // If you want per-mesh indices, do something similar: + // But since original code calls make_quad_indices_buffer(js, quads), we assume it's global and can handle reuse. + + // If any buffer needs a new allocation, we discard reuse and build all new buffers. + int need_new_all = pos_chk.need_new || uv_chk.need_new || color_chk.need_new || need_idx_new; + ret = JS_NewObject(js); - JS_SetProperty(js, ret, pos_atom, make_gpu_buffer(js, posdata, sizeof(*posdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 1)); - JS_SetProperty(js, ret, uv_atom, make_gpu_buffer(js, uvdata, sizeof(*uvdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 1)); - JS_SetProperty(js, ret, color_atom, make_gpu_buffer(js, colordata, sizeof(*colordata) * verts, JS_TYPED_ARRAY_FLOAT32, 0, 1)); - JS_SetProperty(js, ret, indices_atom, make_quad_indices_buffer(js, quads)); + + if (need_new_all) { + // Create all new buffers + JSValue new_pos = make_gpu_buffer(js, posdata, pos_size, JS_TYPED_ARRAY_FLOAT32, 2, 1,0); + JSValue new_uv = make_gpu_buffer(js, uvdata, uv_size, JS_TYPED_ARRAY_FLOAT32, 2, 1,0); + JSValue new_color = make_gpu_buffer(js, colordata, color_size, JS_TYPED_ARRAY_FLOAT32, 0, 1,0); + + JS_SetProperty(js, ret, pos_atom, new_pos); + JS_SetProperty(js, ret, uv_atom, new_uv); + JS_SetProperty(js, ret, color_atom, new_color); + + // Indices + JSValue indices = make_quad_indices_buffer(js, quads); + JS_SetProperty(js, ret, indices_atom, indices); + } else { + // Reuse the old buffers + // Just copy data into existing buffers via their pointers + memcpy(pos_chk.ptr, posdata, pos_size); + memcpy(uv_chk.ptr, uvdata, uv_size); + memcpy(color_chk.ptr, colordata, color_size); + + // Duplicate old references since we're returning a new object + JS_SetProperty(js, ret, pos_atom, JS_DupValue(js, pos_chk.val)); + JS_SetProperty(js, ret, uv_atom, JS_DupValue(js, uv_chk.val)); + JS_SetProperty(js, ret, color_atom, JS_DupValue(js, color_chk.val)); + + // Indices can remain the same if they were also large enough. If using a shared global index buffer: + JSValue indices = make_quad_indices_buffer(js, quads); + JS_SetProperty(js, ret, indices_atom, indices); + } + JS_SetProperty(js, ret, vertices_atom, number2js(js, verts)); JS_SetProperty(js, ret, count_atom, number2js(js, count)); + + // Free temporary CPU arrays + free(posdata); + free(uvdata); + free(colordata); + + // Free old buffer values if they were fetched + if (!JS_IsUndefined(pos_chk.val)) JS_FreeValue(js, pos_chk.val); + if (!JS_IsUndefined(uv_chk.val)) JS_FreeValue(js, uv_chk.val); + if (!JS_IsUndefined(color_chk.val)) JS_FreeValue(js, color_chk.val); + + return ret; ) JSC_CCALL(gpu_driver, @@ -3941,27 +4084,75 @@ JSC_CCALL(gpu_set_pipeline, return JS_ThrowReferenceError(js, "Must use make_pipeline before setting."); ) -JSC_CCALL(gpu_make_shader, - SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self); - SDL_GPUShaderStage stage = !JS_ToBool(js,argv[1]);; +static JSValue js_gpu_make_shader(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { + if (argc < 1 || !JS_IsObject(argv[0])) + return JS_ThrowTypeError(js, "make_shader expects an object with code, stage, num_samplers, num_textures, num_storage_buffers, num_uniform_buffers"); - size_t size; - void *code = JS_GetArrayBuffer(js, &size, argv[0]); + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self); - SDL_GPUShader *shader = SDL_CreateGPUShader(gpu, &(SDL_GPUShaderCreateInfo) { - .code_size = size, - .code = code, - .entrypoint = "main", - .format = SDL_GPU_SHADERFORMAT_SPIRV, - .stage = stage, - .num_samplers = js2number(js,argv[2]), - .num_storage_textures = js2number(js,argv[3]), - .num_storage_buffers = js2number(js,argv[4]), - .num_uniform_buffers = js2number(js,argv[5]) - }); - if (!shader) return JS_ThrowReferenceError(js, "Unable to create shader: %s", SDL_GetError()); - return SDL_GPUShader2js(js,shader); -) + JSValue obj = argv[0]; + + // code + JSValue code_val = JS_GetPropertyStr(js, obj, "code"); + size_t code_size; + void *code_data = JS_GetArrayBuffer(js, &code_size, code_val); + JS_FreeValue(js, code_val); + if (!code_data) + return JS_ThrowTypeError(js, "shader.code must be an ArrayBuffer"); + + // stage + JSValue stage_val = JS_GetPropertyStr(js, obj, "stage"); + const char *stage_str = JS_ToCString(js, stage_val); + SDL_GPUShaderStage stage = SDL_GPU_SHADERSTAGE_VERTEX; + if (stage_str) { + if (!strcmp(stage_str, "vertex")) stage = SDL_GPU_SHADERSTAGE_VERTEX; + else if (!strcmp(stage_str, "fragment")) stage = SDL_GPU_SHADERSTAGE_FRAGMENT; + JS_FreeCString(js, stage_str); + } + JS_FreeValue(js, stage_val); + + // num_samplers + JSValue samplers_val = JS_GetPropertyStr(js, obj, "num_samplers"); + Uint32 samplers = 0; + JS_ToUint32(js, &samplers, samplers_val); + JS_FreeValue(js, samplers_val); + + // num_textures (storage textures) + JSValue textures_val = JS_GetPropertyStr(js, obj, "num_textures"); + Uint32 textures = 0; + JS_ToUint32(js, &textures, textures_val); + JS_FreeValue(js, textures_val); + + // num_storage_buffers + JSValue storage_bufs_val = JS_GetPropertyStr(js, obj, "num_storage_buffers"); + Uint32 storage_bufs = 0; + JS_ToUint32(js, &storage_bufs, storage_bufs_val); + JS_FreeValue(js, storage_bufs_val); + + // num_uniform_buffers + JSValue uniform_bufs_val = JS_GetPropertyStr(js, obj, "num_uniform_buffers"); + Uint32 uniform_bufs = 0; + JS_ToUint32(js, &uniform_bufs, uniform_bufs_val); + JS_FreeValue(js, uniform_bufs_val); + + SDL_GPUShaderCreateInfo info = {0}; + info.code_size = code_size; + info.code = code_data; + info.entrypoint = "main"; + info.format = SDL_GPU_SHADERFORMAT_SPIRV; + info.stage = stage; + info.num_samplers = samplers; + info.num_storage_textures = textures; + info.num_storage_buffers = storage_bufs; + info.num_uniform_buffers = uniform_bufs; + info.props = 0; // No extension properties by default + + SDL_GPUShader *shader = SDL_CreateGPUShader(gpu, &info); + if (!shader) + return JS_ThrowReferenceError(js, "Unable to create shader: %s", SDL_GetError()); + + return SDL_GPUShader2js(js, shader); +} JSC_CCALL(gpu_acquire_cmd_buffer, SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self); @@ -3983,73 +4174,128 @@ static Uint32 atom2usage(JSContext *js, JSAtom atom) { return SDL_GPU_BUFFERUSAGE_VERTEX; } +/* takes argv + 0: a command buffer to write to + 1: a buffer or array of buffers to upload + 2: an optional transfer buffer to use +*/ JSC_CCALL(gpu_upload, - SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self); - SDL_GPUCommandBuffer *cmds = SDL_AcquireGPUCommandBuffer(gpu); - SDL_GPUCopyPass *copypass = SDL_BeginGPUCopyPass(cmds); + JSValue js_cmd = argv[0]; + JSValue js_buffers = argv[1]; + JSValue js_transfer = argv[2]; - JSAtom usage_atom = JS_ValueToAtom(js, argv[1]); - Uint32 flag = atom2usage(js, usage_atom); - JS_FreeAtom(js, usage_atom); + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self); + if (!gpu) + return JS_ThrowTypeError(js, "Invalid GPU device"); - JSValue buffers = argv[0]; // process the entire array of buffers - size_t len = js_arrlen(js,buffers); - SDL_GPUTransferBuffer *transfer_buffers[len]; + SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, js_cmd); + if (!cmds) + return JS_ThrowTypeError(js, "Invalid command buffer"); - for (int i = 0; i < len; i++) { - JSValue js_buf = JS_GetPropertyUint32(js,buffers,i); - JSValue js_idx = JS_GetPropertyStr(js,js_buf, "index"); + if (!JS_IsArray(js, js_buffers)) + return JS_ThrowTypeError(js, "buffers must be an array"); - Uint32 flag; + size_t len = js_arrlen(js, js_buffers); + if (len == 0) + return JS_DupValue(js, js_transfer); // No data to upload, just return existing transfer buffer - if (JS_IsUndefined(js_idx)) - flag = SDL_GPU_BUFFERUSAGE_VERTEX; - else { - flag = SDL_GPU_BUFFERUSAGE_INDEX; - JS_FreeValue(js,js_idx); + struct { + SDL_GPUBuffer *gpu_buffer; + void *data; + size_t size; + } *items = malloc(sizeof(*items) * len); + + if (!items) + return JS_ThrowOutOfMemory(js); + + size_t total_size_needed = 0; + + for (size_t i = 0; i < len; i++) { + JSValue js_buf = JS_GetPropertyUint32(js, js_buffers, i); + + if (JS_IsUndefined(js_buf)) + continue; + + gpu_buffer_unpack(js, gpu, js_buf, &items[i].size, &items[i].data, &items[i].gpu_buffer); + total_size_needed += items[i].size; + JS_FreeValue(js, js_buf); } - + + size_t total_size = 0; + + SDL_GPUTransferBuffer *transfer = js2SDL_GPUTransferBuffer(js,js_transfer); + if (transfer) { + // ensure it's large enough + size_t transfer_size = js_getnum_str(js,js_transfer, "size"); + if (transfer_size < total_size) { + printf("NEED A LARGER TRANSFER BUFFER\n"); + // Need a new one + transfer = SDL_CreateGPUTransferBuffer( gpu, &(SDL_GPUTransferBufferCreateInfo){ + .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, + .size = total_size_needed + } + ); + ret = SDL_GPUTransferBuffer2js(js,transfer); + } else + ret = JS_DupValue(js,js_transfer); // supplied transfer buffer is fine so we use it + } else { + // Need a new one + printf("NEED A NEW TRANSFER BUFFER\n"); + transfer = SDL_CreateGPUTransferBuffer( gpu, &(SDL_GPUTransferBufferCreateInfo){ + .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, + .size = total_size_needed + } + ); + ret = SDL_GPUTransferBuffer2js(js,transfer); - size_t stride, size; - void *data = get_gpu_buffer(js, js_buf, &stride, &size); - - SDL_GPUBuffer *gpu_buffer = SDL_CreateGPUBuffer( - gpu, - &(SDL_GPUBufferCreateInfo) { - .size = size, - .usage = flag, - } - ); - - JS_SetPropertyStr(js,js_buf, "gpu", SDL_GPUBuffer2js(js,gpu_buffer)); - JS_FreeValue(js,js_buf); - - transfer_buffers[i] = SDL_CreateGPUTransferBuffer( - gpu, - &(SDL_GPUTransferBufferCreateInfo) { - .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, - .size = size - } - ); - - void *mapped_data = SDL_MapGPUTransferBuffer(gpu, transfer_buffers[i], false); - memcpy(mapped_data, data, size); - SDL_UnmapGPUTransferBuffer(gpu, transfer_buffers[i]); - - SDL_UploadToGPUBuffer(copypass, - &(SDL_GPUTransferBufferLocation) { .transfer_buffer = transfer_buffers[i], .offset = 0 }, - &(SDL_GPUBufferRegion) { .buffer = gpu_buffer, .offset = 0, .size = size }, - false - ); } - SDL_EndGPUCopyPass(copypass); - SDL_SubmitGPUCommandBuffer(cmds); - - for (int i = 0; i < len; i++) - SDL_ReleaseGPUTransferBuffer(gpu, transfer_buffers[i]); + + SDL_GPUCopyPass *copy_pass = SDL_BeginGPUCopyPass(cmds); + if (!copy_pass) { + free(items); + return JS_ThrowReferenceError(js, "Failed to begin copy pass"); + } + + void *mapped_data = SDL_MapGPUTransferBuffer(gpu, transfer, false); + if (!mapped_data) { + SDL_EndGPUCopyPass(copy_pass); + free(items); + return JS_ThrowReferenceError(js, "Failed to map transfer buffer: %s", SDL_GetError()); + } + + // Copy all data into the mapped transfer buffer + size_t current_offset = 0; + for (size_t i = 0; i < len; i++) { + memcpy(mapped_data + current_offset, items[i].data, items[i].size); + current_offset += items[i].size; + } + + SDL_UnmapGPUTransferBuffer(gpu, transfer); + + // Issue uploads for each item + current_offset = 0; + for (size_t i = 0; i < len; i++) { + SDL_UploadToGPUBuffer( + copy_pass, + &(SDL_GPUTransferBufferLocation){ + .transfer_buffer = transfer, + .offset = current_offset + }, + &(SDL_GPUBufferRegion){ + .buffer = items[i].gpu_buffer, + .offset = 0, + .size = items[i].size + }, + false + ); + current_offset += items[i].size; + } + + SDL_EndGPUCopyPass(copy_pass); + + free(items); ) - static const JSCFunctionListEntry js_SDL_GPUDevice_funcs[] = { MIST_FUNC_DEF(gpu, claim_window, 1), MIST_FUNC_DEF(gpu, make_pipeline, 1), // loads pipeline state into an object @@ -4065,10 +4311,10 @@ static const JSCFunctionListEntry js_SDL_GPUDevice_funcs[] = { MIST_FUNC_DEF(gpu, viewport, 1), MIST_FUNC_DEF(gpu, make_sprite_mesh, 2), MIST_FUNC_DEF(gpu, driver, 0), - MIST_FUNC_DEF(gpu, make_shader, 6), + MIST_FUNC_DEF(gpu, make_shader, 1), MIST_FUNC_DEF(gpu, load_gltf_model, 1), MIST_FUNC_DEF(gpu, acquire_cmd_buffer, 0), - MIST_FUNC_DEF(gpu, upload, 1), + MIST_FUNC_DEF(gpu, upload, 3), }; JSC_CCALL(renderpass_bind_pipeline, @@ -4082,88 +4328,31 @@ JSC_CCALL(renderpass_draw, SDL_DrawGPUIndexedPrimitives(pass, js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]), js2number(js,argv[3]), js2number(js,argv[4])); ) -JSC_CCALL(renderpass_bind_model, - SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js, self); - JSValue model = argv[0]; - - // Helper macro to get and convert a GPU buffer from a property if it exists - #define GET_GPU_BUFFER_IF_EXISTS(NAME) \ - JSValue NAME##_val = JS_GetPropertyStr(js, model, #NAME); \ - SDL_GPUBuffer *NAME##_buf = NULL; \ - if (!JS_IsUndefined(NAME##_val)) { \ - JSValue NAME##_gpu_val = JS_GetPropertyStr(js, NAME##_val, "gpu"); \ - if (!JS_IsUndefined(NAME##_gpu_val)) { \ - NAME##_buf = js2SDL_GPUBuffer(js, NAME##_gpu_val); \ - } \ - JS_FreeValue(js, NAME##_gpu_val); \ - } \ - JS_FreeValue(js, NAME##_val); - - // Attempt to retrieve each attribute buffer - GET_GPU_BUFFER_IF_EXISTS(pos) - GET_GPU_BUFFER_IF_EXISTS(uv) - GET_GPU_BUFFER_IF_EXISTS(color) - GET_GPU_BUFFER_IF_EXISTS(normal) - - // Indices (we assume these must always be present for indexed draws) - JSValue indices_val = JS_GetPropertyStr(js, model, "indices"); - SDL_GPUBuffer *indices_buf = NULL; - if (!JS_IsUndefined(indices_val)) { - JSValue indices_gpu_val = JS_GetPropertyStr(js, indices_val, "gpu"); - if (!JS_IsUndefined(indices_gpu_val)) { - indices_buf = js2SDL_GPUBuffer(js, indices_gpu_val); - JS_FreeValue(js, indices_gpu_val); - } else { - JS_FreeValue(js, indices_val); - return JS_ThrowInternalError(js, "Model has indices but no GPU buffer property."); - } - } else { - JS_FreeValue(js, indices_val); - return JS_ThrowReferenceError(js, "Model must have an 'indices' property."); - } - JS_FreeValue(js, indices_val); - - // Bind each buffer if it exists. We know our pipeline expects: - // slot 0: pos (float3) - // slot 1: uv (float2) - // slot 2: color (float4) - // slot 3: normal (float3) - // - // If a buffer is missing, we just don't bind anything for that slot. - - SDL_GPUBufferBinding binding; - - if (pos_buf) { - binding.buffer = pos_buf; - binding.offset = 0; - SDL_BindGPUVertexBuffers(pass, 0, &binding, 1); // pos at slot 0 +JSC_CCALL(renderpass_bind_buffers, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + int first = js2number(js,argv[0]); + JSValue buffers = argv[1]; + int len = js_arrlen(js,buffers); + SDL_GPUBufferBinding bindings[len]; + for (int i = 0; i < len; i++) { + JSValue buffer = JS_GetPropertyUint32(js,buffers,i); + bindings[i].offset = 0; + gpu_buffer_unpack(js,global_gpu, buffer, NULL, NULL,&bindings[i].buffer); } - if (uv_buf) { - binding.buffer = uv_buf; - binding.offset = 0; - SDL_BindGPUVertexBuffers(pass, 1, &binding, 1); // uv at slot 1 - } + SDL_BindGPUVertexBuffers( + pass, + first, + bindings, + len); +) - if (color_buf) { - binding.buffer = color_buf; - binding.offset = 0; - SDL_BindGPUVertexBuffers(pass, 2, &binding, 1); // color at slot 2 - } - - if (normal_buf) { - binding.buffer = normal_buf; - binding.offset = 0; - SDL_BindGPUVertexBuffers(pass, 3, &binding, 1); // normal at slot 3 - } - - // Bind the index buffer - // If your indices are 16-bit, pass SDL_GPU_INDEXELEMENTSIZE_16BIT - // If they're 32-bit, pass SDL_GPU_INDEXELEMENTSIZE_32BIT. - // Our model_buffer code used uint32 for indices, so let's assume 32-bit. - SDL_BindGPUIndexBuffer(pass, &(SDL_GPUBufferBinding){.buffer = indices_buf, .offset = 0}, SDL_GPU_INDEXELEMENTSIZE_16BIT); - - return JS_UNDEFINED; +JSC_CCALL(renderpass_bind_index_buffer, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + SDL_GPUBufferBinding bind; + bind.offset = 0; + gpu_buffer_unpack(js,global_gpu,argv[0], NULL, NULL, &bind.buffer); + SDL_BindGPUIndexBuffer(pass,&bind,SDL_GPU_INDEXELEMENTSIZE_16BIT); ) JSC_CCALL(renderpass_end, @@ -4171,51 +4360,46 @@ JSC_CCALL(renderpass_end, SDL_EndGPURenderPass(pass); ) - -JSC_CCALL(renderpass_bind_mat, - SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js, self); - JSValue mat = argv[0]; - - // Look for the diffuse property on the material - JSValue diffuse_val = JS_GetPropertyStr(js, mat, "diffuse"); - if (!JS_IsUndefined(diffuse_val)) { - // Retrieve the GPU texture from diffuse.gpu - JSValue diffuse_gpu_val = JS_GetPropertyStr(js, diffuse_val, "texture"); - SDL_GPUTexture *texture = js2SDL_GPUTexture(js, diffuse_gpu_val); - - // Retrieve the sampler from diffuse.sampler - JSValue sampler_val = JS_GetPropertyStr(js, diffuse_val, "sampler"); - SDL_GPUSampler *sampler = NULL; - if (JS_IsUndefined(sampler_val)) - return JS_ThrowReferenceError(js,"Image needs a sampler!"); - sampler = js2SDL_GPUSampler(js, sampler_val); - - // Free JS values now that we have native pointers - JS_FreeValue(js, sampler_val); - JS_FreeValue(js, diffuse_gpu_val); - JS_FreeValue(js, diffuse_val); - - // If both texture and sampler are valid, bind them - if (texture && sampler) { - SDL_BindGPUFragmentSamplers( - pass, - 0, - &(SDL_GPUTextureSamplerBinding){ .texture = texture, .sampler = sampler }, - 1 - ); - } - } else { - // No diffuse property, do nothing - JS_FreeValue(js, diffuse_val); +JSC_CCALL(renderpass_bind_samplers, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + int first_slot = js2number(js,argv[1]); + JSValue arr = argv[2]; + int num = js_arrlen(js,arr); + SDL_GPUTextureSamplerBinding binds[num]; + for (int i = 0; i < num; i++) { + JSValue val = JS_GetPropertyUint32(js,arr,i); + JSValue tex = JS_GetPropertyStr(js,val,"texture"); + JSValue smp = JS_GetPropertyStr(js,val,"sampler"); + binds[i].texture = js2SDL_GPUTexture(js,tex); + binds[i].sampler = js2SDL_GPUSampler(js,smp); + JS_FreeValue(js,tex); + JS_FreeValue(js,smp); + JS_FreeValue(js,val); } + int vertex = JS_ToBool(js,argv[0]); + if (vertex) + SDL_BindGPUVertexSamplers(pass, first_slot, binds, num); + else + SDL_BindGPUFragmentSamplers(pass, first_slot, binds, num); +) + +JSC_CCALL(renderpass_bind_storage_buffers, + +) + +JSC_CCALL(renderpass_bind_storage_textures, + ) static const JSCFunctionListEntry js_SDL_GPURenderPass_funcs[] = { MIST_FUNC_DEF(renderpass, bind_pipeline, 1), - MIST_FUNC_DEF(renderpass, bind_model, 1), - MIST_FUNC_DEF(renderpass, bind_mat, 1), MIST_FUNC_DEF(renderpass, draw, 5), MIST_FUNC_DEF(renderpass, end, 0), + MIST_FUNC_DEF(renderpass, bind_index_buffer, 1), + MIST_FUNC_DEF(renderpass, bind_buffers, 3), + MIST_FUNC_DEF(renderpass, bind_samplers, 3), + MIST_FUNC_DEF(renderpass, bind_storage_buffers, 3), + MIST_FUNC_DEF(renderpass, bind_storage_textures, 3), }; JSC_CCALL(copypass_end, @@ -4226,6 +4410,7 @@ JSC_CCALL(copypass_end, float gw, gh; // On GPUCommandBuffer object +static SDL_GPUTexture *depthframe; JSC_CCALL(cmd_render_pass, SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self); SDL_GPUColorTargetInfo info = {0}; @@ -4243,8 +4428,33 @@ JSC_CCALL(cmd_render_pass, info.texture = swapchain; info.clear_color = (SDL_FColor){1.0,0,0,1.0}; info.load_op = SDL_GPU_LOADOP_CLEAR; - info.store_op = SDL_GPU_STOREOP_STORE; - SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(cmds, &info, 1, NULL); + info.store_op = SDL_GPU_STOREOP_STORE; + + SDL_GPUDepthStencilTargetInfo dinfo = {0}; + dinfo.clear_depth = 0.0; + dinfo.load_op = SDL_GPU_LOADOP_CLEAR; + dinfo.store_op = SDL_GPU_STOREOP_STORE; + dinfo.stencil_load_op = SDL_GPU_LOADOP_CLEAR; + dinfo.stencil_store_op = SDL_GPU_STOREOP_STORE; + dinfo.clear_stencil = 0; + + if (!depthframe) { + depthframe = SDL_CreateGPUTexture(global_gpu, &(SDL_GPUTextureCreateInfo){ + .type = SDL_GPU_TEXTURETYPE_2D, + .format = SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT, + .usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, + .width = gw, + .height = gh, + .layer_count_or_depth = 1, + .num_levels = 1 + }); + if (!depthframe) + return JS_ThrowInternalError(js,"Unable to create depth buffer: %s", SDL_GetError()); + } + + dinfo.texture = depthframe; + + SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(cmds, &info, 1, &dinfo); if (!pass) return JS_ThrowInternalError(js, "Failed to begin render pass"); return SDL_GPURenderPass2js(js,pass); ) @@ -4336,8 +4546,8 @@ transform *tra = js2transform(js, argv[0]); HMM_Mat4 view = HMM_Translate((HMM_Vec3){-tra->pos.x, -tra->pos.y, 0.0f}); // Update uniforms for this batch - app_data data = {0}; - data.vp = HMM_MulM4(proj, view); + shader_globals data = {0}; + data.world_to_projection = HMM_MulM4(proj, view); data.time = SDL_GetTicksNS() / 1000000000.0f; SDL_PushGPUVertexUniformData(cmds, 0, &data, sizeof(data)); ) @@ -4362,8 +4572,8 @@ JSC_CCALL(cmd_camera_perspective, // fov property (assume in degrees, convert to radians) JSValue fov_val = JS_GetPropertyStr(js, camera_obj, "fov"); - if (JS_IsUndefined(fov_val)) - return JS_ThrowReferenceError(js, "Camera object must have an fov property."); +// if (JS_IsUndefined(fov_val)) +// return JS_ThrowReferenceError(js, "Camera object must have an fov property."); float fov_degrees = js2number(js, fov_val); float fov = fov_degrees * (HMM_PI / 180.0f); // convert to radians @@ -4400,8 +4610,8 @@ JSC_CCALL(cmd_camera_perspective, // For a proper camera, though, you'll want to consider orientation as well. // Update uniforms - app_data data = {0}; - data.vp = HMM_MulM4(proj, view); + shader_globals data = {0}; + data.world_to_projection = HMM_MulM4(proj, view); data.time = SDL_GetTicksNS() / 1000000000.0f; SDL_PushGPUVertexUniformData(cmds, 0, &data, sizeof(data)); ) @@ -5486,129 +5696,164 @@ JSC_SCALL(os_model_buffer, return JS_UNDEFINED; } - struct aiMesh *mesh = scene->mMeshes[mesh_idx]; - - if (!mesh->mVertices) { - JS_ThrowInternalError(js, "Mesh has no vertices"); - aiReleaseImport(scene); - return JS_UNDEFINED; - } - - size_t num_verts = mesh->mNumVertices; - float *posdata = NULL; - float *normdata = NULL; - float *uvdata = NULL; - float *colordata = NULL; // New: To provide a default color - Uint16 *indicesdata = NULL; - - // Positions (float3) - posdata = malloc(sizeof(float)*3*num_verts); - for (size_t i = 0; i < num_verts; i++) { - posdata[i*3+0] = mesh->mVertices[i].x; - posdata[i*3+1] = mesh->mVertices[i].y; - posdata[i*3+2] = mesh->mVertices[i].z; - } - - // Normals (float3) - guaranteed by aiProcess_GenNormals, but check anyway - if (mesh->mNormals) { - normdata = malloc(sizeof(float)*3*num_verts); - for (size_t i = 0; i < num_verts; i++) { - normdata[i*3+0] = mesh->mNormals[i].x; - normdata[i*3+1] = mesh->mNormals[i].y; - normdata[i*3+2] = mesh->mNormals[i].z; + JSValue materials[scene->mNumMaterials]; + for (int i = 0; i < scene->mNumMaterials; i++) { + JSValue mat = JS_NewObject(js); + materials[i] = mat; + struct aiMaterial *aimat = scene->mMaterials[i]; + struct aiString texPath; + if (aiGetMaterialTexture(aimat, aiTextureType_DIFFUSE, 0, &texPath,NULL,NULL,NULL,NULL,NULL,NULL) == AI_SUCCESS) { + if (texPath.data[0] != '*') { + JS_SetPropertyStr(js,mat,"diffuse", JS_NewString(js,texPath.data)); + } else { + struct aiTexture *tex = scene->mTextures[atoi(texPath.data+1)]; + if (tex->mHeight == 0) { + void *data = tex->pcData; + size_t size = tex->mWidth; + JS_SetPropertyStr(js,mat,"diffuse", JS_NewArrayBufferCopy(js,data,size)); + } else { + size_t size = tex->mWidth*tex->mHeight*4; + void *data = tex->pcData; + JS_SetPropertyStr(js,mat,"diffuse", JS_NewArrayBufferCopy(js,data,size)); + } + } } - } else { - // Provide a default normal if none are available - normdata = malloc(sizeof(float)*3*num_verts); - for (size_t i = 0; i < num_verts; i++) { - normdata[i*3+0] = 0.0f; - normdata[i*3+1] = 0.0f; - normdata[i*3+2] = 1.0f; + } + // Create an array to hold all the models + JSValue ret_arr = JS_NewArray(js); + + // Loop over all meshes + for (unsigned int m = 0; m < scene->mNumMeshes; m++) { + struct aiMesh *mesh = scene->mMeshes[m]; + if (!mesh->mVertices) { + JS_ThrowInternalError(js, "Mesh %u has no vertices", m); + aiReleaseImport(scene); + return JS_UNDEFINED; } + + size_t num_verts = mesh->mNumVertices; + float *posdata = NULL; + float *normdata = NULL; + float *uvdata = NULL; + float *colordata = NULL; + Uint16 *indicesdata = NULL; + + // Positions (float3) + posdata = malloc(sizeof(float)*3*num_verts); + for (size_t i = 0; i < num_verts; i++) { + posdata[i*3+0] = mesh->mVertices[i].x; + posdata[i*3+1] = mesh->mVertices[i].y; + posdata[i*3+2] = mesh->mVertices[i].z; + } + + // Normals (float3) + if (mesh->mNormals) { + normdata = malloc(sizeof(float)*3*num_verts); + for (size_t i = 0; i < num_verts; i++) { + normdata[i*3+0] = mesh->mNormals[i].x; + normdata[i*3+1] = mesh->mNormals[i].y; + normdata[i*3+2] = mesh->mNormals[i].z; + } + } else { + normdata = malloc(sizeof(float)*3*num_verts); + for (size_t i = 0; i < num_verts; i++) { + normdata[i*3+0] = 0.0f; + normdata[i*3+1] = 0.0f; + normdata[i*3+2] = 1.0f; + } + } + + // UVs (float2) + if (mesh->mTextureCoords[0]) { + uvdata = malloc(sizeof(float)*2*num_verts); + for (size_t i = 0; i < num_verts; i++) { + uvdata[i*2+0] = mesh->mTextureCoords[0][i].x; + uvdata[i*2+1] = mesh->mTextureCoords[0][i].y; + } + } else { + uvdata = malloc(sizeof(float)*2*num_verts); + for (size_t i = 0; i < num_verts; i++) { + uvdata[i*2+0] = 0.0f; + uvdata[i*2+1] = 0.0f; + } + } + + // Colors (float4) + if (mesh->mColors[0]) { + colordata = malloc(sizeof(float)*4*num_verts); + for (size_t i = 0; i < num_verts; i++) { + colordata[i*4+0] = mesh->mColors[0][i].r; + colordata[i*4+1] = mesh->mColors[0][i].g; + colordata[i*4+2] = mesh->mColors[0][i].b; + colordata[i*4+3] = mesh->mColors[0][i].a; + } + } else { + colordata = malloc(sizeof(float)*4*num_verts); + for (size_t i = 0; i < num_verts; i++) { + colordata[i*4+0] = 1.0f; + colordata[i*4+1] = 1.0f; + colordata[i*4+2] = 1.0f; + colordata[i*4+3] = 1.0f; + } + } + + // Indices + size_t num_faces = mesh->mNumFaces; + size_t index_count = num_faces * 3; + indicesdata = malloc(sizeof(Uint16)*index_count); + for (size_t i = 0; i < num_faces; i++) { + const struct aiFace *face = &mesh->mFaces[i]; + indicesdata[i*3+0] = face->mIndices[0]; + indicesdata[i*3+1] = face->mIndices[1]; + indicesdata[i*3+2] = face->mIndices[2]; + } + + // Build a JS object for the mesh data + JSValue js_mesh = JS_NewObject(js); + + // Positions + JS_SetProperty(js, js_mesh, pos_atom, + make_gpu_buffer(js, posdata, sizeof(float)*3*num_verts, JS_TYPED_ARRAY_FLOAT32, 3, 1, 0)); + + // UV + JS_SetProperty(js, js_mesh, uv_atom, + make_gpu_buffer(js, uvdata, sizeof(float)*2*num_verts, JS_TYPED_ARRAY_FLOAT32, 2, 1, 0)); + + // Color + JS_SetProperty(js, js_mesh, color_atom, + make_gpu_buffer(js, colordata, sizeof(float)*4*num_verts, JS_TYPED_ARRAY_FLOAT32, 4, 1, 0)); + + // Normal + JS_SetProperty(js, js_mesh, norm_atom, + make_gpu_buffer(js, normdata, sizeof(float)*3*num_verts, JS_TYPED_ARRAY_FLOAT32, 3, 1, 0)); + + // Indices + JS_SetProperty(js, js_mesh, indices_atom, + make_gpu_buffer(js, indicesdata, sizeof(Uint16)*index_count, JS_TYPED_ARRAY_UINT16, 0, 1, 1)); + + // Metadata + JS_SetProperty(js, js_mesh, vertices_atom, number2js(js, (double)num_verts)); + JS_SetProperty(js, js_mesh, count_atom, number2js(js, (double)index_count)); + + // Build final "model" object with mesh + material + JSValue model_obj = JS_NewObject(js); + JS_SetPropertyStr(js, model_obj, "mesh", js_mesh); + JS_SetPropertyStr(js, model_obj, "material", JS_DupValue(js,materials[mesh->mMaterialIndex])); + + // Place into our return array + JS_SetPropertyUint32(js, ret_arr, m, model_obj); + + // Cleanup (per-mesh) + free(posdata); + free(normdata); + free(uvdata); + free(colordata); + free(indicesdata); } - // UVs (float2) - if (mesh->mTextureCoords[0]) { - uvdata = malloc(sizeof(float)*2*num_verts); - for (size_t i = 0; i < num_verts; i++) { - uvdata[i*2+0] = mesh->mTextureCoords[0][i].x; - uvdata[i*2+1] = mesh->mTextureCoords[0][i].y; - } - } else { - // Default UVs if not present - uvdata = malloc(sizeof(float)*2*num_verts); - for (size_t i = 0; i < num_verts; i++) { - uvdata[i*2+0] = 0.0f; - uvdata[i*2+1] = 0.0f; - } - } - - // Colors (float4) - Assimp may provide vertex colors, but if not, default to white - // Check if there's a color channel - if (mesh->mColors[0]) { - colordata = malloc(sizeof(float)*4*num_verts); - for (size_t i = 0; i < num_verts; i++) { - colordata[i*4+0] = mesh->mColors[0][i].r; - colordata[i*4+1] = mesh->mColors[0][i].g; - colordata[i*4+2] = mesh->mColors[0][i].b; - colordata[i*4+3] = mesh->mColors[0][i].a; - } - } else { - colordata = malloc(sizeof(float)*4*num_verts); - for (size_t i = 0; i < num_verts; i++) { - colordata[i*4+0] = 1.0f; - colordata[i*4+1] = 1.0f; - colordata[i*4+2] = 1.0f; - colordata[i*4+3] = 1.0f; - } - } - - // Indices - size_t num_faces = mesh->mNumFaces; - size_t index_count = num_faces * 3; - indicesdata = malloc(sizeof(Uint16)*index_count); - for (size_t i = 0; i < num_faces; i++) { - const struct aiFace *face = &mesh->mFaces[i]; - indicesdata[i*3+0] = face->mIndices[0]; - indicesdata[i*3+1] = face->mIndices[1]; - indicesdata[i*3+2] = face->mIndices[2]; - } - - // Create a JS object to hold the buffers - ret = JS_NewObject(js); - - // Positions: float3 - JS_SetProperty(js, ret, pos_atom, - make_gpu_buffer(js, posdata, sizeof(float)*3*num_verts, JS_TYPED_ARRAY_FLOAT32, 3, 1)); - - // UV: float2 - JS_SetProperty(js, ret, uv_atom, - make_gpu_buffer(js, uvdata, sizeof(float)*2*num_verts, JS_TYPED_ARRAY_FLOAT32, 2, 1)); - - // Color: float4 - JS_SetProperty(js, ret, color_atom, - make_gpu_buffer(js, colordata, sizeof(float)*4*num_verts, JS_TYPED_ARRAY_FLOAT32, 4, 1)); - - // Normal: float3 - JS_SetProperty(js, ret, norm_atom, - make_gpu_buffer(js, normdata, sizeof(float)*3*num_verts, JS_TYPED_ARRAY_FLOAT32, 3, 1)); - - // Indices: uint32 - JS_SetProperty(js, ret, indices_atom, - make_gpu_buffer(js, indicesdata, sizeof(unsigned int)*index_count, JS_TYPED_ARRAY_UINT16, 0, 1)); - - // Metadata - JS_SetProperty(js, ret, vertices_atom, number2js(js, (double)num_verts)); - JS_SetProperty(js, ret, count_atom, number2js(js, (double)index_count)); - - // Cleanup - free(posdata); - free(normdata); - free(uvdata); - free(colordata); - free(indicesdata); - aiReleaseImport(scene); + + ret = ret_arr; ) JSC_SCALL(os_gltf_buffer, @@ -5649,7 +5894,7 @@ JSC_CCALL(os_make_color_buffer, HMM_Vec4 *buffer = malloc(sizeof(HMM_Vec4) * count); for (int i = 0; i < count; i++) memcpy(buffer+i, &color, sizeof(HMM_Vec4)); - ret = make_gpu_buffer(js,buffer,sizeof(HMM_Vec4)*count,JS_TYPED_ARRAY_FLOAT32,4,0); + ret = make_gpu_buffer(js,buffer,sizeof(HMM_Vec4)*count,JS_TYPED_ARRAY_FLOAT32,4,0,0); ) JSC_CCALL(os_make_line_prim, @@ -5672,9 +5917,9 @@ JSC_CCALL(os_make_line_prim, .closed = JS_ToBool(js,argv[3]) }); - JS_SetPropertyStr(js, prim, "pos", make_gpu_buffer(js,m->positions,sizeof(*m->positions)*m->num_vertices, JS_TYPED_ARRAY_FLOAT32, 2,1)); + JS_SetPropertyStr(js, prim, "pos", make_gpu_buffer(js,m->positions,sizeof(*m->positions)*m->num_vertices, JS_TYPED_ARRAY_FLOAT32, 2,1,0)); - JS_SetPropertyStr(js, prim, "indices", make_gpu_buffer(js,m->triangle_indices,sizeof(*m->triangle_indices)*m->num_triangles*3, JS_TYPED_ARRAY_UINT32, 1,1)); + JS_SetPropertyStr(js, prim, "indices", make_gpu_buffer(js,m->triangle_indices,sizeof(*m->triangle_indices)*m->num_triangles*3, JS_TYPED_ARRAY_UINT32, 1,1,1)); float uv[m->num_vertices*2]; for (int i = 0; i < m->num_vertices; i++) { @@ -5682,7 +5927,7 @@ JSC_CCALL(os_make_line_prim, uv[i*2+1] = m->annotations[i].v_across_curve; } - JS_SetPropertyStr(js, prim, "uv", make_gpu_buffer(js, uv, sizeof(uv), JS_TYPED_ARRAY_FLOAT32,2,1)); + JS_SetPropertyStr(js, prim, "uv", make_gpu_buffer(js, uv, sizeof(uv), JS_TYPED_ARRAY_FLOAT32,2,1,0)); JS_SetPropertyStr(js,prim,"vertices", number2js(js,m->num_vertices)); JS_SetPropertyStr(js,prim,"count", number2js(js,m->num_triangles*3));