This commit is contained in:
2024-12-24 09:48:52 -06:00
parent f3f0777de6
commit a0b610d93f
13 changed files with 1137 additions and 725 deletions

View File

@@ -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]);
};

View File

@@ -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;
};

View File

@@ -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
}

View File

@@ -0,0 +1,10 @@
struct PSInput
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR0;
float3 normal : NORMAL;
float3 gouraud : COLOR1;
};

View File

@@ -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;
}

View File

@@ -0,0 +1,8 @@
#include "common.hlsl"
// Structure for pixel shader input (from vertex shader output).
struct PSInput
{
float2 uv : TEXCOORD0;
float4 color : COLOR0;
};

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

File diff suppressed because it is too large Load Diff