gpu renderer

This commit is contained in:
2025-07-29 19:14:58 -05:00
parent e5c19e7e80
commit cba6f4fd59
4 changed files with 271 additions and 162 deletions

View File

@@ -7,27 +7,12 @@ var surface = use('surface')
var sdl_gpu = use('sdl_gpu')
var io = use('io')
var geometry = use('geometry')
var blob = use('blob')
var os = use('os')
// suppose your rendersurface is W×H pixels, and
// your camera.viewport = { x:0, y:0, width:1, height:1 };
// so vpW = W*1, vpH = H*1
var win_size = {width:500,height:500}
def vpW = 640
def vpH = 360
// how many worldunits from center to left/right?
// if zoom = 1 means 1 worldunit == 1 pixel:
def halfW = vpW * 0.5;
def halfH = vpH * 0.5;
// define your clippingvolume in cameraspace:
def l = -halfW, r = +halfW;
def b = -halfH, t = +halfH;
def n = 0, f = 1;
// Metal wants z in [0,1], so we use the “zerotoone” variant:
function makeOrthoMetal(l,r,b,t,n,f){
return [
2/(r-l), 0, 0, 0,
@@ -37,10 +22,26 @@ function makeOrthoMetal(l,r,b,t,n,f){
]
}
def P = makeOrthoMetal(l,r,b,t,n,f);
function make_camera_pblob(camera) {
def zoom = camera.zoom;
def cw = win_size.width;
def ch = win_size.height;
// how big is the world window?
def world_w = cw / zoom;
def world_h = ch / zoom;
// compute worldspace bounds so that camera.pos lands at the anchor
// anchor.x = 0 → origin at left; 1 → origin at right
def l = camera.pos[0] - camera.anchor[0] * world_w;
def b = camera.pos[1] - camera.anchor[1] * world_h;
def r = l + world_w;
def t = b + world_h;
// now build the Metal/Vulkanstyle ortho (z ∈ [0,1])
def mat = makeOrthoMetal(l, r, b, t, 0, 1);
return geometry.array_blob(mat);
}
var Pblob = geometry.array_blob(P)
log.console(Pblob.length)
var driver = "vulkan"
switch(os.platform()) {
@@ -57,9 +58,9 @@ switch(os.platform()) {
}
var default_sampler = {
min_filter: "nearest",
mag_filter: "nearest",
mipmap: "linear",
min_filter: "linear",
mag_filter: "linear",
mipmap: "nearest",
u: "repeat",
v: "repeat",
w: "repeat",
@@ -67,7 +68,7 @@ var default_sampler = {
max_anisotropy: 0,
compare_op: "none",
min_lod: 0,
max_lod: 0,
max_lod: 2,
anisotropy: false,
compare: false
};
@@ -128,7 +129,7 @@ var default_window = {
tooltip: false, // Tooltip window (requires parent)
popupMenu: false, // Popup menu window (requires parent)
// Graphics API flags (let SDL choose if not specified)
// Graphics API flags (var SDL choose if not specified)
opengl: false, // Force OpenGL context
vulkan: false, // Force Vulkan context
metal: false, // Force Metal context (macOS)
@@ -166,6 +167,7 @@ var pipeline_cache = {}
function upload(copypass, buffer, toblob)
{
stone(toblob)
var trans = new sdl_gpu.transfer_buffer(device, {
size: toblob.length/8,
usage:"upload"
@@ -200,7 +202,7 @@ function make_shader(sh_file)
entrypoint: shader_type == "msl" ? "main0" : "main"
}
shader.gpu = new sdl_gpu.shader(device, shader)
shader[GPU] = new sdl_gpu.shader(device, shader)
shader.reflection = refl;
shader_cache[file] = shader
shader.file = sh_file
@@ -210,13 +212,9 @@ function make_shader(sh_file)
var usepipe
function load_pipeline(config)
{
log.console(usepipe)
if (usepipe) return usepipe
config.vertex = make_shader(config.vertex).gpu
config.fragment = make_shader(config.fragment).gpu
log.console(json.encode(config))
log.console("ANOTHER NEW PIPELINE")
log.console(config)
config.vertex = make_shader(config.vertex)[GPU]
config.fragment = make_shader(config.fragment)[GPU]
usepipe = new sdl_gpu.graphics_pipeline(device, config)
log.console(usepipe)
return usepipe
@@ -327,17 +325,6 @@ var images = {}
var renderer_commands = []
var win_size = {width:500,height:500}
var logical = {width:500,height:500}
function get_img_gpu(img)
{
if (img.gpu) return img.gpu
var surf = new surface(img.cpu)
img.gpu = device.load_texture(surf, 0)
return img.gpu
}
///// input /////
var input_cb
var input_rate = 1/60
@@ -405,7 +392,8 @@ function poll_input() {
}
if (event.type.startsWith('mouse_') && event.pos && event.pos.y)
event.pos.y = -event.pos.y + logical.height
event.pos.y = -event.pos.y + win_size.height
// event.pos.y = -event.pos.y + logical.height
filteredEvents.push(event)
}
@@ -430,7 +418,7 @@ var sprite_pipeline = {
cull: "none",
target: {
color_targets: [
{format: device.swapchain_format(window), blend:disabled_blend_state}
{format: device.swapchain_format(window), blend:alpha_blend_state}
],
},
vertex_buffer_descriptions: [ { slot:0, input_rate: "vertex", instance_step_rate: 0,
@@ -449,97 +437,252 @@ var sprite_pipeline = {
var pipey = load_pipeline(sprite_pipeline)
prosperon.create_batch = function create_batch(draw_cmds, done) {
var img = graphics.texture("pockle")
var pipeline = pipey
var GPU = Symbol()
var geom = geometry.make_rect_quad({x:-320,y:-180,width:640,height:360})
geom.indices = geometry.make_quad_indices(1)
var cur_cam
var cmd_fns = {}
cmd_fns.camera = function(cmd)
{
draw_queue.push(cmd)
}
var cmd_buffer = device.acquire_cmd_buffer()
var new_tex = []
cmd_buffer.push_vertex_uniform_data(0, Pblob)
var pos_buffer = new sdl_gpu.buffer(device, {
vertex: true,
size: geom.xy.length/8
})
var uv_buffer = new sdl_gpu.buffer(device, {
vertex: true,
size: geom.uv.length/8
})
var color_buffer = new sdl_gpu.buffer(device, {
vertex: true,
size: geom.color.length/8
})
var index_buffer = new sdl_gpu.buffer(device, {
index: true,
size: geom.indices.length/8
})
var cpy_pass = cmd_buffer.copy_pass()
upload(cpy_pass, pos_buffer, geom.xy)
upload(cpy_pass, uv_buffer, geom.uv)
upload(cpy_pass, color_buffer, geom.color)
upload(cpy_pass, index_buffer, geom.indices)
if (!img.gpu) {
img.gpu = new sdl_gpu.texture(device, {
width: img.width,
height: img.height,
function get_img_gpu(surface)
{
var full_mip = Math.floor(Math.log2(Math.max(surface.width,surface.height))) + 1
var gpu = new sdl_gpu.texture(device, {
width: surface.width,
height: surface.height,
layers: 1,
mip_levels: 1,
mip_levels: full_mip,
samples: 0,
type: "2d",
format: "rgba8",
sampler: true,
color_target: true
})
var tbuf = new sdl_gpu.transfer_buffer(device, {
size: img.cpu.pixels.length/8,
size: surface.pixels.length/8,
usage: "upload"
})
tbuf.copy_blob(device, img.cpu.pixels)
tbuf.copy_blob(device, surface.pixels)
cpy_pass.upload_to_texture({
copy_pass.upload_to_texture({
transfer_buffer: tbuf,
offset: 0,
pixels_per_row: img.cpu.width,
rows_per_lay: img.cpu.height,
pixels_per_row: surface.width,
rows_per_layer: surface.height,
}, {
texture: img.gpu,
texture: gpu,
mip_level: 0,
layer: 0,
x: 0, y: 0, z: 0,
w: img.cpu.width,
h: img.cpu.height,
w: surface.width,
h: surface.height,
d: 1
}, false);
new_tex.push(gpu)
return gpu
}
var pos_blob
var uv_blob
var color_blob
var index_blob
var draw_queue = []
var index_count = 0
var vertex_count = 0
function render_geom(geom, img)
{
if (!img[GPU]) {
if (img.surface)
img[GPU] = get_img_gpu(img.surface)
else
img[GPU] = get_img_gpu(img.cpu)
}
cpy_pass.end()
pos_blob.write_blob(geom.xy)
uv_blob.write_blob(geom.uv)
color_blob.write_blob(geom.color)
index_blob.write_blob(geom.indices)
var pass = cmd_buffer.swapchain_pass(window)
pass.viewport({
x: 0, y: 0,
width: 640,
height: 360
draw_queue.push({
pipeline:pipey,
texture: img[GPU],
num_indices: geom.num_indices,
first_index: index_count,
vertex_offset: vertex_count
})
pass.bind_pipeline(pipeline)
pass.bind_buffers(0, [{buffer:pos_buffer,offset:0}, {buffer:uv_buffer, offset:0}, {buffer:color_buffer, offset:0}])
pass.bind_index_buffer({buffer:index_buffer,offset:0}, 16)
pass.bind_samplers(false, 0, [{texture:img.gpu, sampler:std_sampler}])
pass.draw_indexed(6, 1, 0, 0, 0)
vertex_count += (geom.xy.length/8) / 8
index_count += geom.num_indices
}
pass.end()
cmd_buffer.submit()
cmd_fns.draw_image = function(cmd)
{
var img = graphics.texture(cmd.image)
var geom = geometry.make_rect_quad({x:cmd.rect.x, y:cmd.rect.y, width: img.width, height: img.height})
geom.indices = geometry.make_quad_indices(1)
geom.num_indices = 6
render_geom(geom, img)
}
cmd_fns.draw_text = function(cmd)
{
if (!cmd.text || !cmd.pos) return
var font = graphics.get_font(cmd.font)
if (!font[GPU])
font[GPU] = get_img_gpu(font.surface)
var mesh = graphics.make_text_buffer(
cmd.text,
cmd.pos,
[cmd.material.color.r, cmd.material.color.g, cmd.material.color.b, cmd.material.color.a],
cmd.wrap || 0,
font
)
render_geom(mesh, font)
}
cmd_fns.tilemap = function(cmd)
{
var geometryCommands = cmd.tilemap.draw()
for (var geomCmd of geometryCommands) {
var img = graphics.texture(geomCmd.image)
if (!img) continue
render_geom(geomCmd.geometry, img)
}
}
cmd_fns.geometry = function(cmd)
{
if (typeof cmd.image == 'object') {
render_geom(cmd.geometry, cmd.image)
return
}
var img = graphics.texture(cmd.image)
if (!img) return
render_geom(cmd.geometry, img)
}
cmd_fns.draw_slice9 = function(cmd)
{
var img = graphics.texture(cmd.image)
if (!img) return
// Use the gpu_slice9 function from geometry module to generate the mesh
var slice_info = {
tile_top: true,
tile_bottom: true,
tile_left: true,
tile_right: true,
tile_center_x: true,
tile_center_y: true
}
// Convert single slice value to LRTB object if needed
var slice_lrtb = cmd.slice
if (typeof cmd.slice == 'number') {
slice_lrtb = {
l: cmd.slice,
r: cmd.slice,
t: cmd.slice,
b: cmd.slice
}
}
var mesh = geometry.slice9(img, cmd.rect, slice_lrtb, slice_info)
render_geom(mesh, img)
}
var copy_pass
prosperon.create_batch = function create_batch(draw_cmds, done) {
pos_blob = new blob
uv_blob = new blob
color_blob = new blob
index_blob = new blob
draw_queue = []
index_count = 0
vertex_count = 0
new_tex = []
var render_queue = device.acquire_cmd_buffer()
copy_pass = render_queue.copy_pass()
for (var cmd of draw_cmds)
if (cmd_fns[cmd.cmd])
cmd_fns[cmd.cmd](cmd)
var pos_buffer = new sdl_gpu.buffer(device,{ vertex:true, size:pos_blob.length/8});
var uv_buffer = new sdl_gpu.buffer(device,{ vertex:true, size:uv_blob.length/8});
var color_buffer = new sdl_gpu.buffer(device,{ vertex:true, size:color_blob.length/8});
var index_buffer = new sdl_gpu.buffer(device,{ index:true, size:index_blob.length/8});
upload(copy_pass, pos_buffer, pos_blob)
upload(copy_pass, uv_buffer, uv_blob)
upload(copy_pass, color_buffer, color_blob)
upload(copy_pass, index_buffer, index_blob)
copy_pass.end();
for (var g of new_tex)
render_queue.generate_mipmaps(g)
var render_pass = render_queue.swapchain_pass(window)
render_pass.bind_pipeline(pipey)
render_pass.bind_buffers(0, [
{ buffer: pos_buffer, offset: 0 },
{ buffer: uv_buffer, offset: 0 },
{ buffer: color_buffer, offset: 0 }
])
render_pass.bind_index_buffer(
{ buffer: index_buffer, offset: 0 }, // the binding itself is in bytes
16 // 16 = Uint32 indices
);
for (var cmd of draw_queue) {
if (cmd.camera) {
render_pass.viewport({
x: cmd.camera.viewport.x*win_size.width,
y: cmd.camera.viewport.y * win_size.height,
width: cmd.camera.viewport.width * win_size.width,
height: cmd.camera.viewport.height * win_size.height
})
cur_cam = make_camera_pblob(cmd.camera)
render_queue.push_vertex_uniform_data(0, cur_cam)
continue
}
render_pass.bind_samplers(false, 0, [{texture:cmd.texture, sampler: std_sampler}])
render_pass.draw_indexed(
cmd.num_indices,
1,
cmd.first_index,
cmd.vertex_offset,
0
)
}
render_pass.end()
render_queue.submit()
if (done) done()
}
@@ -590,30 +733,4 @@ prosperon.set_window = function(config)
if (window_cmds[c]) window_cmds[c](config[c])
}
prosperon.set_renderer = function(config)
{
}
// Function to load textures directly to the renderer
prosperon.load_texture = function(surface_data) {
var surf = new surface(surface_data)
if (!surf) return null
var tex = renderer.load_texture(surf)
if (!tex) return null
// Set pixel mode to nearest for all textures
tex.scaleMode = "nearest"
var tex_id = allocate_id()
resources.texture[tex_id] = tex
return {
id: tex_id,
texture: tex,
width: tex.width,
height: tex.height
}
}
return prosperon

View File

@@ -14,7 +14,6 @@ var cache = {}
// cpu is the surface
graphics.Image = function(surfaceData) {
this.cpu = surfaceData || null;
this.gpu = 0;
this.texture = 0;
this.surface = this.cpu;
this.width = surfaceData?.width || 0;
@@ -24,12 +23,6 @@ graphics.Image = function(surfaceData) {
this[LASTUSE] = time.number();
}
// Add methods to prototype
graphics.Image.prototype.unload_gpu = function() {
this.gpu = 0;
this.texture = 0;
}
graphics.Image.prototype.unload_cpu = function() {
this.cpu = null;
this.surface = null;

View File

@@ -1463,7 +1463,7 @@ static const JSCFunctionListEntry js_geometry_funcs[] = {
MIST_FUNC_DEF(geometry, sprites_to_data, 1),
MIST_FUNC_DEF(geometry, transform_xy_blob, 2),
MIST_FUNC_DEF(gpu, tile, 4),
MIST_FUNC_DEF(gpu, slice9, 3),
MIST_FUNC_DEF(gpu, slice9, 4),
MIST_FUNC_DEF(gpu, make_sprite_mesh, 2),
MIST_FUNC_DEF(gpu, make_sprite_queue, 4),
MIST_FUNC_DEF(geometry, renderitem_push, 3),

View File

@@ -603,7 +603,6 @@ static JSValue js_gpu_graphics_pipeline_constructor(JSContext *js, JSValueConst
vbd[i].pitch = pitch;
vbd[i].input_rate = input_rate;
vbd[i].instance_step_rate = step_rate;
printf("slot %d, pitch %d ...\n", vbd[i].slot, vbd[i].pitch);
}
JS_FreeValue(js, elem);
}