853 lines
24 KiB
JavaScript
853 lines
24 KiB
JavaScript
var render = {}
|
|
|
|
var io = use('io')
|
|
var os = use('os')
|
|
var controller = use('controller')
|
|
|
|
var graphics = use('graphics')
|
|
|
|
var config = use('config.js')
|
|
prosperon.camera = use('camera').make()
|
|
prosperon.camera.size = [config.width,config.height]
|
|
|
|
var base_pipeline = {
|
|
vertex: "sprite.vert",
|
|
fragment: "sprite.frag",
|
|
primitive: "triangle", // point, line, linestrip, triangle, trianglestrip
|
|
fill: true, // false for lines
|
|
depth: {
|
|
compare: "greater_equal", // never/less/equal/less_equal/greater/not_equal/greater_equal/always
|
|
test: false,
|
|
write: false,
|
|
bias: 0,
|
|
bias_slope_scale: 0,
|
|
bias_clamp: 0
|
|
},
|
|
stencil: {
|
|
enabled: true,
|
|
front: {
|
|
compare: "equal", // never/less/equal/less_equal/greater/neq/greq/always
|
|
fail: "keep", // keep/zero/replace/incr_clamp/decr_clamp/invert/incr_wrap/decr_wrap
|
|
depth_fail: "keep",
|
|
pass: "keep"
|
|
},
|
|
back: {
|
|
compare: "equal", // never/less/equal/less_equal/greater/neq/greq/always
|
|
fail: "keep", // keep/zero/replace/incr_clamp/decr_clamp/invert/incr_wrap/decr_wrap
|
|
depth_fail: "keep",
|
|
pass: "keep"
|
|
},
|
|
test: true,
|
|
compare_mask: 0,
|
|
write_mask: 0
|
|
},
|
|
blend: {
|
|
enabled: false,
|
|
src_rgb: "zero", // zero/one/src_color/one_minus_src_color/dst_color/one_minus_dst_color/src_alpha/one_minus_src_alpha/dst_alpha/one_minus_dst_alpha/constant_color/one_minus_constant_color/src_alpha_saturate
|
|
dst_rgb: "zero",
|
|
op_rgb: "add", // add/sub/rev_sub/min/max
|
|
src_alpha: "one",
|
|
dst_alpha: "zero",
|
|
op_alpha: "add"
|
|
},
|
|
cull: "none", // none/front/back
|
|
face: "cw", // cw/ccw
|
|
alpha_to_coverage: false,
|
|
multisample: {
|
|
count: 1, // number of multisamples
|
|
mask: 0xFFFFFFFF,
|
|
domask: false
|
|
},
|
|
label: "scripted pipeline",
|
|
target: {}
|
|
}
|
|
|
|
var sprite_pipeline = Object.create(base_pipeline);
|
|
sprite_pipeline.blend = {
|
|
enabled:true,
|
|
src_rgb: "src_alpha", // zero/one/src_color/one_minus_src_color/dst_color/one_minus_dst_color/src_alpha/one_minus_src_alpha/dst_alpha/one_minus_dst_alpha/constant_color/one_minus_constant_color/src_alpha_saturate
|
|
dst_rgb: "one_minus_src_alpha",
|
|
op_rgb: "add", // add/sub/rev_sub/min/max
|
|
src_alpha: "one",
|
|
dst_alpha: "zero",
|
|
op_alpha: "add"
|
|
};
|
|
|
|
sprite_pipeline.target = {
|
|
color_targets: [{
|
|
format:"rgba8",
|
|
blend:sprite_pipeline.blend
|
|
}],
|
|
depth: "d32 float s8"
|
|
};
|
|
|
|
|
|
var appy = {};
|
|
appy.inputs = {};
|
|
if (os.platform() === "macos") {
|
|
appy.inputs["S-q"] = os.exit;
|
|
}
|
|
|
|
appy.inputs["M-f4"] = os.exit;
|
|
|
|
controller.player[0].control(appy);
|
|
|
|
var default_conf = {
|
|
title:`Prosperon [${prosperon.version}-${prosperon.revision}]`,
|
|
width: 1280,
|
|
height: 720,
|
|
icon: graphics.make_texture(io.slurpbytes('icons/moon.gif')),
|
|
high_dpi:0,
|
|
alpha:1,
|
|
fullscreen:0,
|
|
sample_count:1,
|
|
enable_clipboard:true,
|
|
enable_dragndrop: true,
|
|
max_dropped_files: 1,
|
|
swap_interval: 1,
|
|
name: "Prosperon",
|
|
version:prosperon.version + "-" + prosperon.revision,
|
|
identifier: "world.pockle.prosperon",
|
|
creator: "Pockle World LLC",
|
|
copyright: "Copyright Pockle World 2025",
|
|
type: "application",
|
|
url: "https://github.com/johnbrethauer/prosperon"
|
|
}
|
|
|
|
config.__proto__ = default_conf
|
|
|
|
prosperon.window = prosperon.engine_start(config);
|
|
|
|
var driver = "vulkan"
|
|
switch(os.platform()) {
|
|
case "Linux":
|
|
driver = "vulkan"
|
|
break
|
|
case "Windows":
|
|
// driver = "direct3d12"
|
|
driver = "vulkan"
|
|
break
|
|
case "macOS":
|
|
driver = "metal"
|
|
break
|
|
}
|
|
|
|
render._main = prosperon.window.make_gpu(false,driver)
|
|
prosperon.gpu = render._main
|
|
render._main.window = prosperon.window
|
|
render._main.claim_window(prosperon.window)
|
|
render._main.set_swapchain('sdr', 'vsync')
|
|
|
|
var unit_transform = os.make_transform();
|
|
|
|
var cur = {};
|
|
cur.images = [];
|
|
cur.samplers = [];
|
|
|
|
var tbuffer;
|
|
function full_upload(buffers) {
|
|
var cmds = render._main.acquire_cmd_buffer();
|
|
tbuffer = render._main.upload(cmds, buffers, tbuffer);
|
|
cmds.submit();
|
|
}
|
|
|
|
full_upload[prosperon.DOC] = `Acquire a command buffer and upload the provided data buffers to the GPU, then submit.
|
|
|
|
:param buffers: An array of data buffers to be uploaded.
|
|
:return: None
|
|
`
|
|
|
|
function bind_pipeline(pass, pipeline) {
|
|
make_pipeline(pipeline)
|
|
pass.bind_pipeline(pipeline.gpu)
|
|
pass.pipeline = pipeline;
|
|
}
|
|
|
|
bind_pipeline[prosperon.DOC] = `Ensure the specified pipeline is created on the GPU and bind it to the given render pass.
|
|
|
|
:param pass: The current render pass to bind the pipeline to.
|
|
:param pipeline: The pipeline object containing shader and state info.
|
|
:return: None
|
|
`
|
|
|
|
var main_pass;
|
|
|
|
var cornflower = [62/255,96/255,113/255,1];
|
|
|
|
function get_pipeline_ubo_slot(pipeline, name) {
|
|
if (!pipeline.vertex.reflection.ubos) return;
|
|
for (var i = 0; i < pipeline.vertex.reflection.ubos.length; i++) {
|
|
var ubo = pipeline.vertex.reflection.ubos[i];
|
|
if (ubo.name.endsWith(name))
|
|
return i;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
get_pipeline_ubo_slot[prosperon.DOC] = `Return the index of a uniform buffer block within the pipeline's vertex reflection data by name suffix.
|
|
|
|
:param pipeline: The pipeline whose vertex reflection is inspected.
|
|
:param name: A string suffix to match against the uniform buffer block name.
|
|
:return: The integer index of the matching UBO, or undefined if not found.
|
|
`
|
|
|
|
function transpose4x4(val) {
|
|
var out = [];
|
|
out[0] = val[0]; out[1] = val[4]; out[2] = val[8]; out[3] = val[12];
|
|
out[4] = val[1]; out[5] = val[5]; out[6] = val[9]; out[7] = val[13];
|
|
out[8] = val[2]; out[9] = val[6]; out[10] = val[10];out[11] = val[14];
|
|
out[12] = val[3];out[13] = val[7];out[14] = val[11];out[15] = val[15];
|
|
return out;
|
|
}
|
|
|
|
transpose4x4[prosperon.DOC] = `Return a new 4x4 matrix array that is the transpose of the passed matrix.
|
|
|
|
:param val: An array of length 16 representing a 4x4 matrix in row-major format.
|
|
:return: A new array of length 16 representing the transposed matrix.
|
|
`
|
|
|
|
function ubo_obj_to_array(pipeline, name, obj) {
|
|
var ubo;
|
|
for (var i = 0; i < pipeline.vertex.reflection.ubos.length; i++) {
|
|
ubo = pipeline.vertex.reflection.ubos[i];
|
|
if (ubo.name.endsWith(name)) break;
|
|
}
|
|
var type = pipeline.vertex.reflection.types[ubo.type];
|
|
var len = 0;
|
|
for (var mem of type.members)
|
|
len += type_to_byte_count(mem.type);
|
|
|
|
var buf = new ArrayBuffer(len);
|
|
var view = new DataView(buf);
|
|
|
|
for (var mem of type.members) {
|
|
var val = obj[mem.name];
|
|
if (!val) throw new Error (`Could not find ${mem.name} on supplied object`);
|
|
|
|
if (mem.name === 'model')
|
|
val = transpose4x4(val.array());
|
|
|
|
for (var i = 0; i < val.length; i++)
|
|
view.setFloat32(mem.offset + i*4, val[i],true);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
ubo_obj_to_array[prosperon.DOC] = `Construct an ArrayBuffer containing UBO data from the provided object, matching the pipeline's reflection info.
|
|
|
|
:param pipeline: The pipeline whose vertex reflection is read for UBO structure.
|
|
:param name: The name suffix that identifies the target UBO in the reflection data.
|
|
:param obj: An object whose properties match the UBO members.
|
|
:return: An ArrayBuffer containing packed UBO data.
|
|
`
|
|
|
|
function type_to_byte_count(type) {
|
|
switch (type) {
|
|
case 'float': return 4;
|
|
case 'vec2': return 8;
|
|
case 'vec3': return 12;
|
|
case 'vec4': return 16;
|
|
case 'mat4': return 64;
|
|
default: throw new Error("Unknown or unsupported float-based type: " + type);
|
|
}
|
|
}
|
|
|
|
type_to_byte_count[prosperon.DOC] = `Return the byte size for known float-based types.
|
|
|
|
:param type: A string type identifier (e.g., 'float', 'vec2', 'vec3', 'vec4', 'mat4').
|
|
:return: Integer number of bytes.
|
|
`
|
|
|
|
var sprite_model_ubo = {
|
|
model: unit_transform,
|
|
color: [1,1,1,1]
|
|
};
|
|
|
|
var shader_cache = {};
|
|
var shader_times = {};
|
|
|
|
function make_pipeline(pipeline) {
|
|
if (pipeline.hasOwnProperty("gpu")) return; // this pipeline has already been made
|
|
|
|
if (typeof pipeline.vertex === 'string')
|
|
pipeline.vertex = make_shader(pipeline.vertex);
|
|
if (typeof pipeline.fragment === 'string')
|
|
pipeline.fragment = make_shader(pipeline.fragment)
|
|
|
|
// 1) Reflection data for vertex shader
|
|
var refl = pipeline.vertex.reflection
|
|
if (!refl || !refl.inputs || !Array.isArray(refl.inputs)) {
|
|
pipeline.gpu = render._main.make_pipeline(pipeline);
|
|
return;
|
|
}
|
|
|
|
var inputs = refl.inputs
|
|
var buffer_descriptions = []
|
|
var attributes = []
|
|
|
|
// 2) Build buffer + attribute for each reflection input
|
|
for (var i = 0; i < inputs.length; i++) {
|
|
var inp = inputs[i]
|
|
var typeStr = inp.type
|
|
var nameStr = (inp.name || "").toUpperCase()
|
|
var pitch = 4
|
|
var fmt = "float1"
|
|
|
|
if (typeStr == "vec2") {
|
|
pitch = 8
|
|
fmt = "float2"
|
|
} else if (typeStr == "vec3") {
|
|
pitch = 12
|
|
fmt = "float3"
|
|
} else if (typeStr == "vec4") {
|
|
if (nameStr.indexOf("COLOR") >= 0) {
|
|
pitch = 16
|
|
fmt = "color"
|
|
} else {
|
|
pitch = 16
|
|
fmt = "float4"
|
|
}
|
|
}
|
|
|
|
buffer_descriptions.push({
|
|
slot: i,
|
|
pitch: pitch,
|
|
input_rate: "vertex",
|
|
instance_step_rate: 0,
|
|
name:inp.name.split(".").pop()
|
|
})
|
|
|
|
attributes.push({
|
|
location: inp.location,
|
|
buffer_slot: i,
|
|
format: fmt,
|
|
offset: 0
|
|
})
|
|
}
|
|
|
|
pipeline.vertex_buffer_descriptions = buffer_descriptions
|
|
pipeline.vertex_attributes = attributes
|
|
|
|
pipeline.gpu = render._main.make_pipeline(pipeline);
|
|
}
|
|
|
|
make_pipeline[prosperon.DOC] = `Create and store a GPU pipeline object if it has not already been created.
|
|
|
|
:param pipeline: An object describing the pipeline state, shaders, and reflection data.
|
|
:return: None
|
|
`
|
|
|
|
var shader_type;
|
|
|
|
function make_shader(sh_file) {
|
|
var file = `shaders/${shader_type}/${sh_file}.${shader_type}`
|
|
if (shader_cache[file]) return shader_cache[file]
|
|
var refl = json.decode(io.slurp(`shaders/reflection/${sh_file}.json`))
|
|
|
|
var shader = {
|
|
code: io.slurpbytes(file),
|
|
format: shader_type,
|
|
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,
|
|
entrypoint: shader_type === "msl" ? "main0" : "main"
|
|
}
|
|
console.log(`making shader ${sh_file} of format ${shader_type}`)
|
|
|
|
shader.gpu = render._main.make_shader(shader)
|
|
shader.reflection = refl;
|
|
shader_cache[file] = shader
|
|
shader.file = sh_file
|
|
return shader
|
|
}
|
|
|
|
make_shader[prosperon.DOC] = `Load and compile a shader from disk, caching the result. Reflective metadata is also loaded.
|
|
|
|
:param sh_file: The base filename (without extension) of the shader to compile.
|
|
:return: A shader object with GPU and reflection data attached.
|
|
`
|
|
|
|
// helpful render devices. width and height in pixels; diagonal in inches.
|
|
render.device = {
|
|
pc: { width: 1920, height: 1080 },
|
|
macbook_m2: { width: 2560, height: 1664, diagonal: 13.6 },
|
|
ds_top: { width: 400, height: 240, diagonal: 3.53 },
|
|
ds_bottom: { width: 320, height: 240, diagonal: 3.02 },
|
|
playdate: { width: 400, height: 240, diagonal: 2.7 },
|
|
switch: { width: 1280, height: 720, diagonal: 6.2 },
|
|
switch_lite: { width: 1280, height: 720, diagonal: 5.5 },
|
|
switch_oled: { width: 1280, height: 720, diagonal: 7 },
|
|
dsi: { width: 256, height: 192, diagonal: 3.268 },
|
|
ds: { width: 256, height: 192, diagonal: 3 },
|
|
dsixl: { width: 256, height: 192, diagonal: 4.2 },
|
|
ipad_air_m2: { width: 2360, height: 1640, diagonal: 11.97 },
|
|
iphone_se: { width: 1334, height: 750, diagonal: 4.7 },
|
|
iphone_12_pro: { width: 2532, height: 1170, diagonal: 6.06 },
|
|
iphone_15: { width: 2556, height: 1179, diagonal: 6.1 },
|
|
gba: { width: 240, height: 160, diagonal: 2.9 },
|
|
gameboy: { width: 160, height: 144, diagonal: 2.48 },
|
|
gbc: { width: 160, height: 144, diagonal: 2.28 },
|
|
steamdeck: { width: 1280, height: 800, diagonal: 7 },
|
|
vita: { width: 960, height: 544, diagonal: 5 },
|
|
psp: { width: 480, height: 272, diagonal: 4.3 },
|
|
imac_m3: { width: 4480, height: 2520, diagonal: 23.5 },
|
|
macbook_pro_m3: { width: 3024, height: 1964, diagonal: 14.2 },
|
|
ps1: { width: 320, height: 240, diagonal: 5 },
|
|
ps2: { width: 640, height: 480 },
|
|
snes: { width: 256, height: 224 },
|
|
gamecube: { width: 640, height: 480 },
|
|
n64: { width: 320, height: 240 },
|
|
c64: { width: 320, height: 200 },
|
|
macintosh: { width: 512, height: 342 },
|
|
gamegear: { width: 160, height: 144, diagonal: 3.2 }
|
|
};
|
|
|
|
render.device.doc = `Device resolutions given as [x,y,inches diagonal].`;
|
|
|
|
var render_queue = [];
|
|
var hud_queue = [];
|
|
|
|
var current_queue = render_queue;
|
|
|
|
var std_sampler = {
|
|
min_filter: "nearest",
|
|
mag_filter: "nearest",
|
|
mipmap: "linear",
|
|
u: "repeat",
|
|
v: "repeat",
|
|
w: "repeat",
|
|
mip_bias: 0,
|
|
max_anisotropy: 0,
|
|
compare_op: "none",
|
|
min_lod: 0,
|
|
max_lod: 0,
|
|
anisotropy: false,
|
|
compare: false
|
|
};
|
|
|
|
function upload_model(model) {
|
|
var bufs = [];
|
|
for (var i in model) {
|
|
if (typeof model[i] !== 'object') continue;
|
|
bufs.push(model[i]);
|
|
}
|
|
render._main.upload(this, bufs);
|
|
}
|
|
|
|
upload_model[prosperon.DOC] = `Upload all buffer-like properties of the given model to the GPU.
|
|
|
|
:param model: An object whose buffer properties are to be uploaded.
|
|
:return: None
|
|
`
|
|
|
|
function bind_model(pass, pipeline, model) {
|
|
var buffers = pipeline.vertex_buffer_descriptions;
|
|
var bufs = [];
|
|
if (buffers)
|
|
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);
|
|
}
|
|
|
|
bind_model[prosperon.DOC] = `Bind the model's vertex and index buffers for the given pipeline and render pass.
|
|
|
|
:param pass: The current render pass.
|
|
:param pipeline: The pipeline object with vertex buffer descriptions.
|
|
:param model: The model object containing matching buffers and an index buffer.
|
|
:return: None
|
|
`
|
|
|
|
function bind_mat(pass, pipeline, mat) {
|
|
var imgs = [];
|
|
var refl = pipeline.fragment.reflection;
|
|
if (refl.separate_images) {
|
|
for (var i of refl.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);
|
|
}
|
|
}
|
|
|
|
bind_mat[prosperon.DOC] = `Bind the material images and samplers needed by the pipeline's fragment shader.
|
|
|
|
:param pass: The current render pass.
|
|
:param pipeline: The pipeline whose fragment shader reflection indicates required textures.
|
|
:param mat: An object mapping the required image names to {texture, sampler}.
|
|
:return: None
|
|
`
|
|
|
|
function group_sprites_by_texture(sprites, mesh) {
|
|
if (sprites.length === 0) return;
|
|
for (var i = 0; i < sprites.length; i++) {
|
|
sprites[i].mesh = mesh;
|
|
sprites[i].first_index = i*6;
|
|
sprites[i].num_indices = 6;
|
|
}
|
|
return;
|
|
// The code below is an alternate approach to grouping by image. Currently not in use.
|
|
/*
|
|
var groups = [];
|
|
var group = {image:sprites[0].image, first_index:0};
|
|
var count = 1;
|
|
for (var i = 1; i < sprites.length; i++) {
|
|
if (sprites[i].image === group.image) {
|
|
count++;
|
|
continue;
|
|
}
|
|
group.num_indices = count*6;
|
|
var newgroup = {image:sprites[i].image, first_index:group.first_index+group.num_indices};
|
|
group = newgroup;
|
|
groups.push(group);
|
|
count=1;
|
|
}
|
|
group.num_indices = count*6;
|
|
return groups;
|
|
*/
|
|
}
|
|
|
|
group_sprites_by_texture[prosperon.DOC] = `Assign each sprite to the provided mesh, generating index data as needed.
|
|
|
|
:param sprites: An array of sprite objects.
|
|
:param mesh: A mesh object (pos, color, uv, indices, etc.) to link to each sprite.
|
|
:return: None
|
|
`
|
|
|
|
var main_color = {
|
|
type:"2d",
|
|
format: "rgba8",
|
|
layers: 1,
|
|
mip_levels: 1,
|
|
samples: 0,
|
|
sampler:true,
|
|
color_target:true
|
|
};
|
|
|
|
var main_depth = {
|
|
type: "2d",
|
|
format: "d32 float s8",
|
|
layers:1,
|
|
mip_levels:1,
|
|
samples:0,
|
|
sampler:true,
|
|
depth_target:true
|
|
};
|
|
|
|
function render_camera(cmds, camera) {
|
|
var pass;
|
|
delete camera.target // TODO: HORRIBLE
|
|
if (!camera.target) {
|
|
main_color.width = main_depth.width = camera.size.x;
|
|
main_color.height = main_depth.height = camera.size.y;
|
|
camera.target = {
|
|
color_targets: [{
|
|
texture: render._main.texture(main_color),
|
|
mip_level:0,
|
|
layer: 0,
|
|
load:"clear",
|
|
store:"store",
|
|
clear: cornflower
|
|
}],
|
|
depth_stencil: {
|
|
texture: render._main.texture(main_depth),
|
|
clear:1,
|
|
load:"dont_care",
|
|
store:"dont_care",
|
|
stencil_load:"dont_care",
|
|
stencil_store:"dont_care",
|
|
stencil_clear:0
|
|
}
|
|
};
|
|
}
|
|
|
|
var buffers = [];
|
|
buffers = buffers.concat(graphics.queue_sprite_mesh(render_queue));
|
|
var unique_meshes = [...new Set(render_queue.map(x => x.mesh))];
|
|
for (var q of unique_meshes)
|
|
buffers = buffers.concat([q.pos, q.color, q.uv, q.indices]);
|
|
|
|
buffers = buffers.concat(graphics.queue_sprite_mesh(hud_queue));
|
|
for (var q of hud_queue)
|
|
if (q.type === 'geometry') buffers = buffers.concat([q.mesh.pos, q.mesh.color, q.mesh.uv, q.mesh.indices]);
|
|
|
|
full_upload(buffers)
|
|
|
|
var pass = cmds.render_pass(camera.target);
|
|
|
|
var pipeline = sprite_pipeline;
|
|
bind_pipeline(pass,pipeline);
|
|
|
|
var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer');
|
|
if (typeof camslot !== 'undefined')
|
|
cmds.camera(camera, camslot);
|
|
|
|
modelslot = get_pipeline_ubo_slot(pipeline, "model");
|
|
if (typeof modelslot !== 'undefined') {
|
|
var ubo = ubo_obj_to_array(pipeline, 'model', sprite_model_ubo);
|
|
cmds.push_vertex_uniform_data(modelslot, ubo);
|
|
}
|
|
|
|
var mesh;
|
|
var img;
|
|
var modelslot;
|
|
|
|
cmds.push_debug_group("draw")
|
|
for (var group of render_queue) {
|
|
if (mesh != group.mesh) {
|
|
mesh = group.mesh;
|
|
bind_model(pass,pipeline,mesh);
|
|
}
|
|
|
|
if (group.image && img != group.image) {
|
|
img = group.image;
|
|
img.sampler = std_sampler;
|
|
bind_mat(pass,pipeline,{diffuse:img});
|
|
}
|
|
|
|
pass.draw_indexed(group.num_indices, 1, group.first_index, 0, 0);
|
|
}
|
|
cmds.pop_debug_group()
|
|
|
|
cmds.push_debug_group("hud")
|
|
var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer');
|
|
if (typeof camslot !== 'undefined')
|
|
cmds.hud(camera.size, camslot);
|
|
|
|
for (var group of hud_queue) {
|
|
if (mesh != group.mesh) {
|
|
mesh = group.mesh;
|
|
bind_model(pass,pipeline,mesh);
|
|
}
|
|
|
|
if (group.image && img != group.image) {
|
|
img = group.image;
|
|
img.sampler = std_sampler;
|
|
bind_mat(pass,pipeline,{diffuse:img});
|
|
}
|
|
|
|
pass.draw_indexed(group.num_indices, 1, group.first_index, 0, 0);
|
|
}
|
|
cmds.pop_debug_group();
|
|
|
|
pass?.end();
|
|
|
|
render_queue = [];
|
|
hud_queue = [];
|
|
}
|
|
|
|
render_camera[prosperon.DOC] = `Render a scene using the provided camera, drawing both render queue and HUD queue items.
|
|
|
|
:param cmds: A command buffer obtained from the GPU context.
|
|
:param camera: The camera object (with size, optional target, etc.).
|
|
:return: None
|
|
`
|
|
|
|
var imgui = use('imgui')
|
|
if (imgui) imgui.init(render._main, prosperon.window);
|
|
|
|
var swaps = [];
|
|
function gpupresent() {
|
|
os.clean_transforms();
|
|
var cmds = render._main.acquire_cmd_buffer();
|
|
render_camera(cmds, prosperon.camera);
|
|
var swapchain_tex = cmds.acquire_swapchain();
|
|
if (!swapchain_tex)
|
|
cmds.cancel();
|
|
else {
|
|
var torect = prosperon.camera.draw_rect(prosperon.window.size);
|
|
torect.texture = swapchain_tex;
|
|
if (swapchain_tex) {
|
|
cmds.blit({
|
|
src: prosperon.camera.target.color_targets[0].texture,
|
|
dst: torect,
|
|
filter:"nearest",
|
|
load: "clear"
|
|
});
|
|
|
|
if (imgui) {
|
|
cmds.push_debug_group("imgui")
|
|
imgui.prepend(cmds);
|
|
var pass = cmds.render_pass({
|
|
color_targets:[{texture:swapchain_tex}]});
|
|
imgui.endframe(cmds,pass);
|
|
pass.end();
|
|
cmds.pop_debug_group()
|
|
}
|
|
}
|
|
cmds.submit()
|
|
}
|
|
}
|
|
|
|
gpupresent[prosperon.DOC] = `Perform the per-frame rendering and present the final swapchain image, including imgui pass if available.
|
|
|
|
:return: None
|
|
`
|
|
|
|
var stencil_write = {
|
|
compare: "always",
|
|
fail_op: "replace",
|
|
depth_fail_op: "replace",
|
|
pass_op: "replace"
|
|
};
|
|
|
|
var stencil_writer = function stencil_writer(ref) {
|
|
var pipe = Object.create(base_pipeline);
|
|
Object.assign(pipe, {
|
|
stencil: {
|
|
enabled: true,
|
|
front: stencil_write,
|
|
back: stencil_write,
|
|
write:true,
|
|
read:true,
|
|
ref:ref
|
|
},
|
|
write_mask: colormask.none
|
|
});
|
|
return pipe;
|
|
}.hashify();
|
|
|
|
render.stencil_writer = stencil_writer;
|
|
|
|
// objects by default draw where the stencil buffer is 0
|
|
render.fillmask = function fillmask(ref) {
|
|
var pipe = stencil_writer(ref);
|
|
render.use_shader('screenfill.cg', pipe);
|
|
render.draw(shape.quad);
|
|
}
|
|
|
|
render.fillmask[prosperon.DOC] = `Draw a fullscreen shape using a 'screenfill' shader to populate the stencil buffer with a given reference.
|
|
|
|
:param ref: The stencil reference value to write.
|
|
:return: None
|
|
`
|
|
|
|
var stencil_invert = {
|
|
compare: "always",
|
|
fail_op: "invert",
|
|
depth_fail_op: "invert",
|
|
pass_op: "invert"
|
|
};
|
|
|
|
render.mask = function mask(image, pos, scale, rotation = 0, ref = 1) {
|
|
if (typeof image === 'string')
|
|
image = graphics.texture(image);
|
|
|
|
var tex = image.texture;
|
|
if (scale) scale = scale.div([tex.width,tex.height]);
|
|
else scale = [1,1,1]
|
|
|
|
var pipe = stencil_writer(ref);
|
|
render.use_shader('sprite.cg', pipe);
|
|
var t = os.make_transform();
|
|
t.trs(pos, undefined, scale);
|
|
set_model(t);
|
|
render.use_mat({
|
|
diffuse:image.texture,
|
|
rect: image.rect,
|
|
shade: Color.white
|
|
});
|
|
render.draw(shape.quad);
|
|
}
|
|
|
|
render.mask[prosperon.DOC] = `Draw an image to the stencil buffer, marking its area with a specified reference value.
|
|
|
|
:param image: A texture or string path (which is converted to a texture).
|
|
:param pos: The translation (x, y) for the image placement.
|
|
:param scale: Optional scaling applied to the texture.
|
|
:param rotation: Optional rotation in radians (unused by default).
|
|
:param ref: The stencil reference value to write.
|
|
:return: None
|
|
`
|
|
|
|
render.viewport = function(rect) {
|
|
render._main.viewport(rect);
|
|
}
|
|
|
|
render.viewport[prosperon.DOC] = `Set the GPU viewport to the specified rectangle.
|
|
|
|
:param rect: A rectangle [x, y, width, height].
|
|
:return: None
|
|
`
|
|
|
|
render.scissor = function(rect) {
|
|
render.viewport(rect)
|
|
}
|
|
|
|
render.scissor[prosperon.DOC] = `Set the GPU scissor region to the specified rectangle (alias of render.viewport).
|
|
|
|
:param rect: A rectangle [x, y, width, height].
|
|
:return: None
|
|
`
|
|
|
|
var imdebug = function imdebug() {
|
|
imtoggle("Physics", debug, "draw_phys");
|
|
imtoggle("Bouning boxes", debug, "draw_bb");
|
|
imtoggle("Names", debug, "draw_names");
|
|
imtoggle("Sprite nums", debug, "sprite_nums");
|
|
imtoggle("Debug overlay", debug, "show");
|
|
imtoggle("Show ur names", debug, "urnames");
|
|
};
|
|
|
|
var observed_tex = undefined;
|
|
|
|
var debug = {}
|
|
debug.console = false
|
|
|
|
// Some initialization
|
|
shader_type = render._main.shader_format()[0];
|
|
|
|
std_sampler = render._main.make_sampler({
|
|
min_filter: "nearest",
|
|
mag_filter: "nearest",
|
|
mipmap_mode: "nearest",
|
|
address_mode_u: "repeat",
|
|
address_mode_v: "repeat",
|
|
address_mode_w: "repeat"
|
|
});
|
|
|
|
render._main.present = gpupresent;
|
|
|
|
tracy.gpu_init()
|
|
|
|
render.queue = function(cmd) {
|
|
if (Array.isArray(cmd))
|
|
for (var i of cmd) current_queue.push(i)
|
|
else
|
|
current_queue.push(cmd)
|
|
}
|
|
|
|
render.queue[prosperon.DOC] = `Enqueue one or more draw commands. These commands are batched until render_camera is called.
|
|
|
|
:param cmd: Either a single command object or an array of command objects.
|
|
:return: None
|
|
`
|
|
|
|
render.setup_draw = function() {
|
|
current_queue = render_queue;
|
|
prosperon.draw();
|
|
}
|
|
|
|
render.setup_draw[prosperon.DOC] = `Switch the current queue to the primary scene render queue, then invoke 'prosperon.draw' if defined.
|
|
|
|
:return: None
|
|
`
|
|
|
|
render.setup_hud = function() {
|
|
current_queue = hud_queue;
|
|
prosperon.hud();
|
|
}
|
|
|
|
render.setup_hud[prosperon.DOC] = `Switch the current queue to the HUD render queue, then invoke 'prosperon.hud' if defined.
|
|
|
|
:return: None
|
|
`
|
|
|
|
return render |