sdl 3 gpu

This commit is contained in:
2025-07-28 16:34:29 -05:00
parent 5ae95aee01
commit 09c3d5cc4e
8 changed files with 1420 additions and 1350 deletions

View File

@@ -328,7 +328,7 @@ sources = []
src += [
'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c',
'render.c','simplex.c','spline.c', 'transform.c','cell.c', 'wildmatch.c',
'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_input.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_fd.c', 'qjs_os.c', 'qjs_actor.c',
'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_input.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_sdl_gpu.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_fd.c', 'qjs_os.c', 'qjs_actor.c',
'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c', 'qjs_layout.c'
]
# quirc src

View File

@@ -2,11 +2,7 @@ var math = use('math')
var color = use('color')
var draw = {}
draw[cell.DOC] = `
A collection of 2D drawing functions that create drawing command lists.
These are pure functions that return plain JavaScript objects representing
drawing operations. No rendering or actor communication happens here.
`
var current_list = []
// Clear current list
@@ -43,15 +39,6 @@ var rect_def = {
radius: 0
}
var slice9_info = {
tile_top:true,
tile_bottom:true,
tile_left:true,
tile_right:true,
tile_center_x:true,
tile_center_right:true,
}
var image_info = {
tile_x: false,
tile_y: false,
@@ -125,6 +112,15 @@ draw.rectangle = function render_rectangle(rect, defl, material) {
})
}
var slice9_info = {
tile_top:true,
tile_bottom:true,
tile_left:true,
tile_right:true,
tile_center_x:true,
tile_center_right:true,
}
draw.slice9 = function slice9(image, rect = [0,0], slice = 0, info = slice9_info, material) {
if (!image) throw Error('Need an image to render.')

View File

@@ -1,10 +1,63 @@
var prosperon = {}
// This file is hard coded for the SDL renderer case
// This file is hard coded for the SDL GPU case
var video = use('sdl_video')
var imgui = use('imgui')
var surface = use('surface')
var sdl_gpu = use('sdl_gpu')
var io = use('io')
var os = use('os')
var driver = "vulkan"
switch(os.platform()) {
case "Linux":
driver = "vulkan"
break
case "Windows":
// driver = "direct3d12"
driver = "vulkan"
break
case "macOS":
driver = "metal"
break
}
var default_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
};
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
};
var default_window = {
// Basic properties
@@ -59,14 +112,251 @@ var default_window = {
var win_config = arg[0] || {}
win_config.__proto__ = default_window
win_config.metal = true
var window = new video.window(win_config)
var renderer = window.make_renderer()
var device = new sdl_gpu.gpu({
shaders_msl:true,
shaders_metallib:true,
name: "metal"
})
device.claim_window(window)
device.set_swapchain(window, 'sdr', 'vsync')
var shader_type = device.shader_format()[0]
shader_type = 'msl'
var std_sampler = new sdl_gpu.sampler(device, default_sampler)
// Shader and pipeline cache
var shader_cache = {}
var pipeline_cache = {}
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"
}
shader.gpu = context.make_shader(shader)
shader.reflection = refl;
shader_cache[file] = shader
shader.file = sh_file
return shader
}
function load_shader(vert, frag, pipeline_config) {
// Default to sprite pipeline config if not provided
pipeline_config = pipeline_config || sprite_pipeline_config
// Check cache first
var cache_key = `${vert}:${frag}:${json.encode(pipeline_config)}`
if (pipeline_cache[cache_key])
return pipeline_cache[cache_key]
// Load shader reflections
var vert_reflection = load_shader_reflection(vert, 'vert')
var frag_reflection = load_shader_reflection(frag, 'frag')
if (!vert_reflection || !frag_reflection)
return null
// Load shaders based on platform
var vert_path = `accio/shaders/${shader_type}/${vert}.vert.${shader_type}`
var frag_path = `accio/shaders/${shader_type}/${frag}.frag.${shader_type}`
if (!io.exists(vert_path) || !io.exists(frag_path)) {
log.error(`Shaders not found: ${vert_path}, ${frag_path}`)
return null
}
var vert_code = io.slurpbytes(vert_path)
var frag_code = io.slurpbytes(frag_path)
// Extract shader info from reflection
var vert_shader_info = {
stage: "vertex",
code: vert_code,
entrypoint: "main0",
format: shader_type,
num_samplers: 0,
num_textures: 0,
num_storage_buffers: 0,
num_uniform_buffers: 0
}
log.console(json.encode(vert_reflection))
// Count fragment shader resources
var num_samplers = 0
var num_textures = 0
if (frag_reflection.separate_images)
num_textures = frag_reflection.separate_images.length
if (frag_reflection.separate_samplers)
num_samplers = frag_reflection.separate_samplers.length
var frag_shader_info = {
stage: "fragment",
code: frag_code,
entrypoint: "main0",
format: shader_type,
num_samplers: num_samplers,
num_textures: num_textures,
num_storage_buffers: 0,
num_uniform_buffers: 0
}
// Check shader cache
var vert_cache_key = `${vert}:vert`
var frag_cache_key = `${frag}:frag`
var vert_shader = shader_cache[vert_cache_key]
if (!vert_shader) {
log.console(json.encode(vert_shader_info))
vert_shader = new sdl_gpu.shader(device, vert_shader_info)
if (!vert_shader) {
log.error(`Failed to create vertex shader: ${vert}`)
return null
}
shader_cache[vert_cache_key] = vert_shader
}
var frag_shader = shader_cache[frag_cache_key]
if (!frag_shader) {
frag_shader = new sdl_gpu.shader(device, frag_shader_info)
if (!frag_shader) {
log.error(`Failed to create fragment shader: ${frag}`)
return null
}
shader_cache[frag_cache_key] = frag_shader
}
// Create graphics pipeline using composition
var pipeline_info = create_pipeline_config({
vertex: vert_shader,
fragment: frag_shader,
blend: pipeline_config.blend,
target: pipeline_config.target,
label: pipeline_config.label
})
// Ensure target is properly set
if (!pipeline_info.target || !pipeline_info.target.color_targets) {
log.error("Pipeline target configuration is missing or invalid")
log.console("pipeline_config:", json.encode(pipeline_config))
return null
}
// Set up vertex input layout based on reflection
var vertex_stride = 0
var vertex_attributes = []
// Build vertex attributes from reflection
if (vert_reflection.inputs) {
// Add attributes from reflection
for (var input of vert_reflection.inputs) {
var format = "float2" // default
var size = 8
if (input.type == "vec2") {
format = "float2"
size = 8
} else if (input.type == "vec4") {
format = "float4"
size = 16
}
vertex_attributes.push({
location: input.location,
buffer_slot: 0,
format: format,
offset: vertex_stride
})
vertex_stride += size
}
}
pipeline_info.vertex_buffer_descriptions = [{
slot: 0,
pitch: vertex_stride,
input_rate: "vertex",
instance_step_rate: 0
}]
pipeline_info.vertex_attributes = vertex_attributes
log.console("Creating pipeline with info:")
log.console(json.encode(pipeline_info))
log.console("Target info:")
log.console(json.encode(pipeline_info.target))
log.console("Has color_targets?", pipeline_info.target.color_targets ? "yes" : "no")
var pipeline = new sdl_gpu.graphics_pipeline(device, pipeline_info)
if (!pipeline) {
log.error(`Failed to create graphics pipeline for ${vert}/${frag}`)
return null
}
// Cache the pipeline
pipeline_cache[cache_key] = pipeline
return pipeline
}
// Load default sprite shaders
var sprite_gpu_pipeline = load_shader('sprite', 'sprite')
// Public function to get shader pipelines
prosperon.get_shader_pipeline = function(vert, frag, pipeline_config) {
return load_shader(vert, frag, pipeline_config)
}
// Export pipeline components for custom configs
prosperon.pipeline = {
depth_states: {
default: default_depth_state,
depth_test: {
compare: "less",
test: true,
write: true,
bias: 0,
bias_slope_scale: 0,
bias_clamp: 0
}
},
blend_states: {
disabled: disabled_blend_state,
alpha: alpha_blend_state,
additive: {
enabled: true,
src_rgb: "src_alpha",
dst_rgb: "one",
op_rgb: "add",
src_alpha: "one",
dst_alpha: "one",
op_alpha: "add"
}
},
stencil_states: {
default: default_stencil_state
},
create_config: create_pipeline_config
}
// Initialize ImGui with the window and renderer
imgui.init(window, renderer)
imgui.newframe()
//imgui.init(window, renderer)
//imgui.newframe()
var os = use('os');
var io = use('io');
var rasterize = use('rasterize');
var time = use('time')
@@ -79,6 +369,102 @@ var graphics = use('graphics')
var camera = {}
prosperon.scissor = function(rect) {
device.scissor(rect)
}
// Pipeline component definitions
var default_depth_state = {
compare: "always", // never/less/equal/less_equal/greater/not_equal/greater_equal/always
test: false,
write: false,
bias: 0,
bias_slope_scale: 0,
bias_clamp: 0
}
var default_stencil_state = {
compare: "always", // 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"
}
var disabled_blend_state = {
enabled: false,
src_rgb: "zero",
dst_rgb: "zero",
op_rgb: "add",
src_alpha: "one",
dst_alpha: "zero",
op_alpha: "add"
}
var alpha_blend_state = {
enabled: true,
src_rgb: "src_alpha",
dst_rgb: "one_minus_src_alpha",
op_rgb: "add",
src_alpha: "one",
dst_alpha: "zero",
op_alpha: "add"
}
var default_multisample_state = {
count: 1,
mask: 0xFFFFFFFF,
domask: false
}
// Helper function to create pipeline config
function create_pipeline_config(options) {
var config = {
vertex: options.vertex,
fragment: options.fragment,
primitive: options.primitive || "triangle",
fill: options.fill ?? true,
depth: options.depth || default_depth_state,
stencil: {
enabled: options.stencil_enabled ?? false,
front: options.stencil_front || default_stencil_state,
back: options.stencil_back || default_stencil_state,
test: options.stencil_test ?? false,
compare_mask: options.stencil_compare_mask ?? 0,
write_mask: options.stencil_write_mask ?? 0
},
blend: options.blend || disabled_blend_state,
cull: options.cull || "none",
face: options.face || "cw",
alpha_to_coverage: options.alpha_to_coverage ?? false,
multisample: options.multisample || default_multisample_state,
label: options.label || "pipeline",
target: options.target || {}
}
// Ensure target has required properties
if (!config.target.color_targets) {
config.target.color_targets = [{
format: "rgba8",
blend: config.blend
}]
}
return config
}
// Sprite pipeline configuration
var sprite_pipeline_config = {
blend: alpha_blend_state,
target: {
color_targets: [{
format: "rgba8",
blend: alpha_blend_state
}],
depth: "d32 float s8" // C code expects "depth" not "depth_stencil_format"
},
label: "sprite pipeline"
}
function updateCameraMatrix(cam) {
def win_w = logical.width
def win_h = logical.height
@@ -152,167 +538,12 @@ var renderer_commands = []
var win_size = {width:500,height:500}
var logical = {width:500,height:500}
// Convert high-level draw commands to low-level renderer commands
function translate_draw_commands(commands) {
renderer_commands.length = 0
commands.forEach(function(cmd) {
if (cmd.material && cmd.material.color && typeof cmd.material.color == 'object') {
renderer.drawColor = cmd.material.color
}
switch(cmd.cmd) {
case "camera":
updateCameraMatrix(cmd.camera, win_size.width, win_size.height)
break
case "draw_rect":
cmd.rect = worldToScreenRect(cmd.rect, camera)
renderer.fillRect(cmd.rect)
break
case "draw_line":
var points = cmd.points.map(p => {
var pt = worldToScreenPoint(p, camera)
return[pt.x, pt.y]
})
renderer.line(points)
break
case "draw_point":
cmd.pos = worldToScreenPoint(cmd.pos, camera)
renderer.point(cmd.pos)
break
case "draw_image":
var img = graphics.texture(cmd.image)
if (!img.gpu) {
function get_img_gpu(img)
{
if (img.gpu) return img.gpu
var surf = new surface(img.cpu)
img.gpu = renderer.load_texture(surf)
}
var gpu = img.gpu
if (!cmd.scale) cmd.scale = {x:1,y:1}
cmd.rect.width ??= img.width
cmd.rect.height ??= img.height
cmd.rect.width = cmd.rect.width * cmd.scale.x
cmd.rect.height = cmd.rect.height * cmd.scale.y
cmd.rect = worldToScreenRect(cmd.rect, camera)
renderer.texture(
gpu,
img.rect,
cmd.rect,
0,
{x:0,y:0}
)
break
case "draw_text":
if (!cmd.text) break
if (!cmd.pos) break
// Get font from the font string (e.g., "smalle.16")
var font = graphics.get_font(cmd.font)
if (!font.gpu) {
var surf = new surface(font.surface)
font.gpu = renderer.load_texture(surf)
}
var gpu = font.gpu
// Create text geometry buffer
var text_mesh = graphics.make_text_buffer(
cmd.text,
{x: cmd.pos.x, y: cmd.pos.y},
[cmd.material.color.r, cmd.material.color.g, cmd.material.color.b, cmd.material.color.a],
cmd.wrap || 0,
font
)
if (!text_mesh) break
if (text_mesh.xy.length == 0) break
// Transform XY coordinates using camera matrix
var camera_params = [camera.a, camera.c, camera.e, camera.f]
var transformed_xy = geometry.transform_xy_blob(text_mesh.xy, camera_params)
// Create transformed geometry object
var geom = {
xy: transformed_xy,
xy_stride: text_mesh.xy_stride,
uv: text_mesh.uv,
uv_stride: text_mesh.uv_stride,
color: text_mesh.color,
color_stride: text_mesh.color_stride,
indices: text_mesh.indices,
num_vertices: text_mesh.num_vertices,
num_indices: text_mesh.num_indices,
size_indices: text_mesh.size_indices
}
renderer.geometry_raw(gpu, geom.xy, geom.xy_stride, geom.color, geom.color_stride, geom.uv, geom.uv_stride, geom.num_vertices, geom.indices, geom.num_indices, geom.size_indices)
break
case "tilemap":
// Get cached geometry commands from tilemap
var geometryCommands = cmd.tilemap.draw()
// Process each geometry command (one per texture)
for (var geomCmd of geometryCommands) {
var img = graphics.texture(geomCmd.image)
if (!img) continue
// Get GPU texture following draw_image pattern
if (!img.gpu) {
var surf = new surface(img.cpu)
img.gpu = renderer.load_texture(surf)
}
var gpu = img.gpu
// Transform geometry through camera and send to renderer
var geom = geomCmd.geometry
// Transform XY coordinates using camera matrix
var camera_params = [camera.a, camera.c, camera.e, camera.f]
var transformed_xy = geometry.transform_xy_blob(geom.xy, camera_params)
// Render directly instead of pushing to commands
renderer.geometry_raw(gpu, transformed_xy, geom.xy_stride, geom.color, geom.color_stride, geom.uv, geom.uv_stride, geom.num_vertices, geom.indices, geom.num_indices, geom.size_indices)
}
break
case "geometry":
var gpu
if (cmd.texture_id) {
// If texture_id provided, assume it's already a GPU texture
gpu = cmd.texture_id
} else {
// Fall back to looking up by image path
var img = graphics.texture(cmd.image)
if (!img) break
// Get GPU texture following draw_image pattern
if (!img.gpu) {
var surf = new surface(img.cpu)
img.gpu = renderer.load_texture(surf)
}
gpu = img.gpu
}
// Transform geometry through camera and send to renderer
var geom = cmd.geometry
// Transform XY coordinates using camera matrix
var camera_params = [camera.a, camera.c, camera.e, camera.f]
var transformed_xy = geometry.transform_xy_blob(geom.xy, camera_params)
// Render directly instead of pushing to commands
renderer.geometry_raw(gpu, transformed_xy, geom.xy_stride, geom.color, geom.color_stride, geom.uv, geom.uv_stride, geom.num_vertices, geom.indices, geom.num_indices, geom.size_indices)
break
}
})
return renderer_commands
img.gpu = device.load_texture(surf, 0)
return img.gpu
}
///// input /////
@@ -322,10 +553,12 @@ function poll_input() {
var evs = input.get_events()
// Filter and transform events
if (renderer && Array.isArray(evs)) {
if (Array.isArray(evs)) {
var filteredEvents = []
var wantMouse = imgui.wantmouse()
var wantKeys = imgui.wantkeys()
// var wantMouse = imgui.wantmouse()
// var wantKeys = imgui.wantkeys()
var wantMouse = false
var wantKeys = false
for (var i = 0; i < evs.length; i++) {
var event = evs[i]
@@ -354,15 +587,15 @@ function poll_input() {
event.type == 'mouse_button_up' ||
event.type == 'mouse_wheel')) {
// Convert window coordinates to renderer logical coordinates
var logicalPos = renderer.coordsFromWindow(event.pos)
event.pos = logicalPos
// var logicalPos = renderer.coordsFromWindow(event.pos)
// event.pos = logicalPos
}
// Handle drop events which also have position
if (event.pos && (event.type == 'drop_file' ||
event.type == 'drop_text' ||
event.type == 'drop_position')) {
var logicalPos = renderer.coordsFromWindow(event.pos)
event.pos = logicalPos
// var logicalPos = renderer.coordsFromWindow(event.pos)
// event.pos = logicalPos
}
// Handle window events
@@ -399,19 +632,95 @@ prosperon.input = function(fn)
poll_input()
}
// 2) helper to build & send a batch, then call done()
prosperon.create_batch = function create_batch(draw_cmds, done) {
renderer.drawColor = {r:0.1,g:0.1,b:0.15,a:1}
renderer.clear()
var cmd_buffer = device.acquire_cmd_buffer()
var swapchain_texture = cmd_buffer.acquire_swapchain(window)
if (draw_cmds && draw_cmds.length)
var commands = translate_draw_commands(draw_cmds)
var img = graphics.texture("pockle")
var gpu_tex = get_img_gpu(img)
var geom = geometry.sprites_to_data([{
pos: {x: 0, y: 0},
texture: img,
color: {r:1,g:1,b:1,a:1}
}])
renderer.drawColor = {r:1,g:1,b:1,a:1}
imgui.endframe(renderer)
imgui.newframe()
device.upload(cmd_buffer, [geom])
renderer.present()
var pass = cmd_buffer.render_pass({
color_targets: [{
texture: swapchain_texture,
clear_color: {r:0.1, g:0.1, b:0.15, a:1},
load_op: "clear",
store_op: "store"
}]
})
if (!swapchain_texture) {
cmd_buffer.cancel()
} else {
}
// Begin render pass
var render_pass = cmd_buffer.render_pass({
color_targets: [{
texture: swapchain_texture,
clear_color: {r: 0.1, g: 0.1, b: 0.15, a: 1.0},
load_op: "clear",
store_op: "store"
}]
})
render_pass.bind_pipeline(sprite_gpu_pipeline)
// Set viewport
render_pass.viewport({
x: 0, y: 0,
w: logical.width,
h: logical.height
})
// Process draw_image commands
for (var cmd of draw_cmds) {
if (cmd.cmd != "draw_image") continue
var img = graphics.texture(cmd.image)
var gpu_tex = get_img_gpu(img)
if (!gpu_tex) continue
// Set up sprite dimensions
cmd.rect.width ??= img.width
cmd.rect.height ??= img.height
cmd.rect.width = cmd.rect.width * (cmd.scale?.x ?? 1)
cmd.rect.height = cmd.rect.height * (cmd.scale?.y ?? 1)
cmd.rect = worldToScreenRect(cmd.rect, camera)
var geom = geometry.sprites_to_data([{
pos: {x: cmd.rect.x, y: cmd.rect.y},
texture: img,
color: {r:1,g:1,b:1,a:1}
}])
log.console(json.encode(geom))
render_pass.bind_buffers(0, [geom], device)
// Bind texture and sampler
render_pass.bind_samplers(false, 0, [{
texture: gpu_tex,
sampler: std_sampler
}])
// Draw the sprite
log.console("DRWA INDEX")
render_pass.draw(geom.num_vertices, 1, 0, 0, 0) // 6 vertices, 1 instance
log.console("DREW")
}
render_pass.end()
cmd_buffer.submit()
if (done) done()
}
@@ -462,22 +771,8 @@ prosperon.set_window = function(config)
if (window_cmds[c]) window_cmds[c](config[c])
}
var renderer_cmds = {
resolution(e) {
logical.width = e.width
logical.height = e.height
renderer.logicalPresentation = {...e}
}
}
prosperon.set_renderer = function(config)
{
for (var c in config)
if (renderer_cmds[c]) renderer_cmds[c](config[c])
}
prosperon.init = function() {
// No longer needed since we initialize directly
}
// Function to load textures directly to the renderer

View File

@@ -1,655 +0,0 @@
var render = {}
var io = use('io')
var controller = use('controller')
var tracy = use('tracy')
var graphics = use('graphics')
var imgui = use('imgui')
var transform = use('transform')
var color = use('color')
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"
};
var context;
sprite_pipeline.target = {
color_targets: [{
format:"rgba8",
blend:sprite_pipeline.blend
}],
depth: "d32 float s8"
};
var driver = "vulkan"
switch(os.platform()) {
case "Linux":
driver = "vulkan"
break
case "Windows":
// driver = "direct3d12"
driver = "vulkan"
break
case "macOS":
driver = "metal"
break
}
var unit_transform = new transform;
var cur = {};
cur.images = [];
cur.samplers = [];
var tbuffer;
function full_upload(buffers) {
var cmds = context.acquire_cmd_buffer();
tbuffer = context.upload(cmds, buffers, tbuffer);
cmds.submit();
}
function bind_pipeline(pass, pipeline) {
make_pipeline(pipeline)
pass.bind_pipeline(pipeline.gpu)
pass.pipeline = pipeline;
}
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 null;
}
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;
}
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;
}
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);
}
}
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 = context.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 = context.make_pipeline(pipeline);
}
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"
}
shader.gpu = context.make_shader(shader)
shader.reflection = refl;
shader_cache[file] = shader
shader.file = sh_file
return shader
}
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]);
}
context.upload(this, bufs);
}
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);
}
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);
}
}
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;
*/
}
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: context.texture(main_color),
mip_level:0,
layer: 0,
load:"clear",
store:"store",
clear: cornflower
}],
depth_stencil: {
texture: context.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 (camslot != null)
cmds.camera(camera, camslot);
modelslot = get_pipeline_ubo_slot(pipeline, "model");
if (modelslot != null) {
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 (camslot != null)
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 = [];
}
var swaps = [];
render.present = function() {
os.clean_transforms();
var cmds = context.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) { // draws any imgui commands present
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()
}
}
var stencil_write = {
compare: "always",
fail_op: "replace",
depth_fail_op: "replace",
pass_op: "replace"
};
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();
// objects by default draw where the stencil buffer is 0
function fillmask(ref) {
var pipe = stencil_writer(ref);
render.use_shader('screenfill.cg', pipe);
render.draw(shape.quad);
}
var stencil_invert = {
compare: "always",
fail_op: "invert",
depth_fail_op: "invert",
pass_op: "invert"
};
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 = new transform;
t.trs(pos, null, scale);
set_model(t);
render.use_mat({
diffuse:image.texture,
rect: image.rect,
shade: color.white
});
render.draw(shape.quad);
}
render.viewport = function(rect) {
context.viewport(rect);
}
render.scissor = function(rect) {
render.viewport(rect)
}
var std_sampler
if (tracy) 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.setup_draw = function() {
current_queue = render_queue;
prosperon.draw();
}
render.setup_hud = function() {
current_queue = hud_queue;
prosperon.hud();
}
render.initialize = function(config)
{
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: "game",
url: "https://prosperon.dev"
}
config.__proto__ = default_conf
prosperon.camera = use('ext/camera').make()
prosperon.camera.size = [config.width,config.height]
prosperon.window = prosperon.engine_start(config)
context = prosperon.window.make_gpu(false,driver)
context.window = prosperon.window
context.claim_window(prosperon.window)
context.set_swapchain('sdr', 'vsync')
if (imgui) imgui.init(context, prosperon.window)
shader_type = context.shader_format()[0];
std_sampler = context.make_sampler({
min_filter: "nearest",
mag_filter: "nearest",
mipmap_mode: "nearest",
address_mode_u: "repeat",
address_mode_v: "repeat",
address_mode_w: "repeat"
});
}
return render

View File

@@ -390,11 +390,6 @@ char *js2strdup(JSContext *js, JSValue v) {
#include "qjs_macros.h"
void SDL_GPUCommandBuffer_free(JSRuntime *rt, SDL_GPUCommandBuffer *c)
{
}
QJSCLASS(font,)
QJSCLASS(datastream,)
@@ -1613,6 +1608,7 @@ JSC_CCALL(os_value_id,
#include "qjs_socket.h"
#include "qjs_nota.h"
#include "qjs_layout.h"
#include "qjs_sdl_gpu.h"
JSValue js_imgui_use(JSContext *js);
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
@@ -1650,6 +1646,7 @@ void ffi_load(JSContext *js)
arrput(rt->module_registry, MISTLINE(text));
arrput(rt->module_registry, MISTLINE(wota));
arrput(rt->module_registry, MISTLINE(nota));
arrput(rt->module_registry, MISTLINE(sdl_gpu));
// power user
arrput(rt->module_registry, MISTLINE(js));

File diff suppressed because it is too large Load Diff

View File

@@ -721,7 +721,7 @@ JSC_CCALL(input_get_events,
while (SDL_PollEvent(&event)) {
// Process event with ImGui first
gui_input(&event);
// gui_input(&event);
WotaBuffer wb = event2wota(&event);
JSValue event_obj = wota2value(js, wb.data);

View File

@@ -80,6 +80,8 @@ static SDL_BlendMode js2blendmode(JSContext *js, JSValue v);
// Window constructor function
static JSValue js_window_constructor(JSContext *js, JSValueConst new_target, int argc, JSValueConst *argv)
{
SDL_Window *www = SDL_CreateWindow("prosperon", 500, 500, 0);
return SDL_Window2js(js, www);
if (argc < 1 || !JS_IsObject(argv[0]))
return JS_ThrowTypeError(js, "Window constructor requires an object argument");
@@ -251,6 +253,8 @@ static JSValue js_window_constructor(JSContext *js, JSValueConst new_target, int
}
JS_FreeValue(js, text_input);
printf("created window %p\n", window);
return window_obj;
}