render update
This commit is contained in:
@@ -53,7 +53,7 @@ clay.contain = layout.contain;
|
||||
|
||||
clay.draw = function draw(fn)
|
||||
{
|
||||
size = [prosperon.logical.x, prosperon.logical.y]
|
||||
var size = [prosperon.logical.x, prosperon.logical.y]
|
||||
lay_ctx.reset();
|
||||
boxes = [];
|
||||
var root = lay_ctx.item();
|
||||
@@ -69,7 +69,7 @@ clay.draw = function draw(fn)
|
||||
id:root,
|
||||
config:root_config
|
||||
});
|
||||
|
||||
fn()
|
||||
lay_ctx.run();
|
||||
|
||||
// Adjust bounding boxes for padding
|
||||
|
||||
@@ -1,5 +1,71 @@
|
||||
var prosperon = {}
|
||||
|
||||
// This file is hard coded for the SDL renderer case
|
||||
|
||||
var video = use('sdl_video')
|
||||
var imgui = use('imgui')
|
||||
var surface = use('surface')
|
||||
|
||||
var default_window = {
|
||||
// Basic properties
|
||||
title: "Prosperon Window",
|
||||
width: 640,
|
||||
height: 480,
|
||||
|
||||
// Position - can be numbers or "centered"
|
||||
x: null, // SDL_WINDOWPOS_null by default
|
||||
y: null, // SDL_WINDOWPOS_null by default
|
||||
|
||||
// Window behavior flags
|
||||
resizable: true,
|
||||
fullscreen: false,
|
||||
hidden: false,
|
||||
borderless: false,
|
||||
alwaysOnTop: false,
|
||||
minimized: false,
|
||||
maximized: false,
|
||||
|
||||
// Input grabbing
|
||||
mouseGrabbed: false,
|
||||
keyboardGrabbed: false,
|
||||
|
||||
// Display properties
|
||||
highPixelDensity: false,
|
||||
transparent: false,
|
||||
opacity: 1.0, // 0.0 to 1.0
|
||||
|
||||
// Focus behavior
|
||||
notFocusable: false,
|
||||
|
||||
// Special window types (mutually exclusive)
|
||||
utility: false, // Utility window (not in taskbar)
|
||||
tooltip: false, // Tooltip window (requires parent)
|
||||
popupMenu: false, // Popup menu window (requires parent)
|
||||
|
||||
// Graphics API flags (let SDL choose if not specified)
|
||||
opengl: false, // Force OpenGL context
|
||||
vulkan: false, // Force Vulkan context
|
||||
metal: false, // Force Metal context (macOS)
|
||||
|
||||
// Advanced properties
|
||||
parent: null, // Parent window for tooltips/popups/modal
|
||||
modal: false, // Modal to parent window (requires parent)
|
||||
externalGraphicsContext: false, // Use external graphics context
|
||||
|
||||
// Input handling
|
||||
textInput: true, // Enable text input on creation
|
||||
}
|
||||
|
||||
var win_config = arg[0] || {}
|
||||
win_config.__proto__ = default_window
|
||||
|
||||
var window = new video.window(win_config)
|
||||
var renderer = window.make_renderer()
|
||||
|
||||
// Initialize ImGui with the window and renderer
|
||||
imgui.init(window, renderer)
|
||||
imgui.newframe()
|
||||
|
||||
var os = use('os');
|
||||
var io = use('io');
|
||||
var rasterize = use('rasterize');
|
||||
@@ -7,9 +73,9 @@ var time = use('time')
|
||||
var tilemap = use('tilemap')
|
||||
var geometry = use('geometry')
|
||||
var res = use('resources')
|
||||
var input = use('input')
|
||||
|
||||
var video = arg[0]
|
||||
var graphics = use('graphics', arg[0])
|
||||
var graphics = use('graphics')
|
||||
|
||||
var camera = {}
|
||||
|
||||
@@ -88,17 +154,11 @@ var logical = {width:500,height:500}
|
||||
|
||||
// Convert high-level draw commands to low-level renderer commands
|
||||
function translate_draw_commands(commands) {
|
||||
if (!graphics) return
|
||||
|
||||
renderer_commands.length = 0
|
||||
|
||||
commands.forEach(function(cmd) {
|
||||
if (cmd.material && cmd.material.color) {
|
||||
renderer_commands.push({
|
||||
op: "set",
|
||||
prop: "drawColor",
|
||||
value: cmd.material.color
|
||||
})
|
||||
if (cmd.material && cmd.material.color && typeof cmd.material.color == 'object') {
|
||||
renderer.drawColor = cmd.material.color
|
||||
}
|
||||
switch(cmd.cmd) {
|
||||
case "camera":
|
||||
@@ -107,91 +167,29 @@ function translate_draw_commands(commands) {
|
||||
|
||||
case "draw_rect":
|
||||
cmd.rect = worldToScreenRect(cmd.rect, camera)
|
||||
// Handle rectangles with optional rounding and thickness
|
||||
if (cmd.opt && cmd.opt.radius && cmd.opt.radius > 0) {
|
||||
// Rounded rectangle
|
||||
var thickness = (cmd.opt.thickness == 0) ? 0 : (cmd.opt.thickness || 1)
|
||||
var raster_result = rasterize.round_rect(cmd.rect, cmd.opt.radius, thickness)
|
||||
|
||||
if (raster_result.type == 'rect') {
|
||||
renderer_commands.push({
|
||||
op: "fillRect",
|
||||
data: {rect: raster_result.data}
|
||||
})
|
||||
} else if (raster_result.type == 'rects') {
|
||||
raster_result.data.forEach(function(rect) {
|
||||
renderer_commands.push({
|
||||
op: "fillRect",
|
||||
data: {rect: rect}
|
||||
})
|
||||
})
|
||||
}
|
||||
} else if (cmd.opt && cmd.opt.thickness && cmd.opt.thickness > 0) {
|
||||
// Outlined rectangle
|
||||
var raster_result = rasterize.outline_rect(cmd.rect, cmd.opt.thickness)
|
||||
|
||||
if (raster_result.type == 'rect') {
|
||||
renderer_commands.push({
|
||||
op: "fillRect",
|
||||
data: {rect: raster_result.data}
|
||||
})
|
||||
} else if (raster_result.type == 'rects') {
|
||||
renderer_commands.push({
|
||||
op: "rects",
|
||||
data: {rects: raster_result.data}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
renderer_commands.push({
|
||||
op: "fillRect",
|
||||
data: {rect: cmd.rect}
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
case "draw_circle":
|
||||
case "draw_ellipse":
|
||||
cmd.pos = worldToScreenPoint(cmd.pos, camera)
|
||||
// Rasterize ellipse to points or rects
|
||||
var radii = cmd.radii || [cmd.radius, cmd.radius]
|
||||
var raster_result = rasterize.ellipse(cmd.pos, radii, cmd.opt || {})
|
||||
|
||||
if (raster_result.type == 'points') {
|
||||
renderer_commands.push({
|
||||
op: "point",
|
||||
data: {points: raster_result.data}
|
||||
})
|
||||
} else if (raster_result.type == 'rects') {
|
||||
// Use 'rects' operation for multiple rectangles
|
||||
renderer_commands.push({
|
||||
op: "rects",
|
||||
data: {rects: raster_result.data}
|
||||
})
|
||||
}
|
||||
renderer.fillRect(cmd.rect)
|
||||
break
|
||||
|
||||
case "draw_line":
|
||||
renderer_commands.push({
|
||||
op: "line",
|
||||
data: {points: cmd.points.map(p => {
|
||||
var points = cmd.points.map(p => {
|
||||
var pt = worldToScreenPoint(p, camera)
|
||||
return [pt.x, pt.y]
|
||||
})}
|
||||
return[pt.x, pt.y]
|
||||
})
|
||||
renderer.line(points)
|
||||
break
|
||||
|
||||
case "draw_point":
|
||||
cmd.pos = worldToScreenPoint(cmd.pos, camera)
|
||||
renderer_commands.push({
|
||||
op: "point",
|
||||
data: {points: [cmd.pos]}
|
||||
})
|
||||
renderer.point(cmd.pos)
|
||||
break
|
||||
|
||||
case "draw_image":
|
||||
var img = graphics.texture(cmd.image)
|
||||
if (!img.gpu) {
|
||||
var surf = new surface(img.cpu)
|
||||
img.gpu = renderer.load_texture(surf)
|
||||
}
|
||||
var gpu = img.gpu
|
||||
if (!gpu) break
|
||||
|
||||
if (!cmd.scale) cmd.scale = {x:1,y:1}
|
||||
cmd.rect.width ??= img.width
|
||||
@@ -199,56 +197,14 @@ function translate_draw_commands(commands) {
|
||||
cmd.rect.width = cmd.rect.width * cmd.scale.x
|
||||
cmd.rect.height = cmd.rect.height * cmd.scale.y
|
||||
cmd.rect = worldToScreenRect(cmd.rect, camera)
|
||||
|
||||
if (cmd.info && (cmd.info.tile_x || cmd.info.tile_y)) {
|
||||
// Use tiling functionality
|
||||
var tile_buffer = geometry.tile(
|
||||
img,
|
||||
img.rect, // source rect in UV coords
|
||||
cmd.rect, // destination rect in screen coords
|
||||
{
|
||||
repeat_x: cmd.info.tile_x || false,
|
||||
repeat_y: cmd.info.tile_y || false
|
||||
}
|
||||
renderer.texture(
|
||||
gpu,
|
||||
img.rect,
|
||||
cmd.rect,
|
||||
0,
|
||||
{x:0,y:0}
|
||||
)
|
||||
|
||||
if (tile_buffer) {
|
||||
// Transform XY coordinates using camera matrix
|
||||
var camera_params = [camera.a, camera.c, camera.e, camera.f]
|
||||
var transformed_xy = geometry.transform_xy_blob(tile_buffer.xy, camera_params)
|
||||
|
||||
// Create transformed geometry object
|
||||
var transformed_geom = {
|
||||
xy: transformed_xy,
|
||||
xy_stride: tile_buffer.xy_stride,
|
||||
uv: tile_buffer.uv,
|
||||
uv_stride: tile_buffer.uv_stride,
|
||||
color: tile_buffer.color,
|
||||
color_stride: tile_buffer.color_stride,
|
||||
indices: tile_buffer.indices,
|
||||
num_vertices: tile_buffer.num_vertices,
|
||||
num_indices: tile_buffer.num_indices,
|
||||
size_indices: tile_buffer.size_indices,
|
||||
texture_id: gpu
|
||||
}
|
||||
|
||||
renderer_commands.push({
|
||||
op: "geometry_raw",
|
||||
data: transformed_geom
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Normal non-tiled rendering
|
||||
renderer_commands.push({
|
||||
op: "texture",
|
||||
data: {
|
||||
texture_id: gpu,
|
||||
dst: cmd.rect,
|
||||
src: img.rect
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case "draw_text":
|
||||
@@ -257,7 +213,11 @@ function translate_draw_commands(commands) {
|
||||
|
||||
// Get font from the font string (e.g., "smalle.16")
|
||||
var font = graphics.get_font(cmd.font)
|
||||
if (!font || !font.texture || !font.texture.id) break
|
||||
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(
|
||||
@@ -277,7 +237,7 @@ function translate_draw_commands(commands) {
|
||||
var transformed_xy = geometry.transform_xy_blob(text_mesh.xy, camera_params)
|
||||
|
||||
// Create transformed geometry object
|
||||
var transformed_geom = {
|
||||
var geom = {
|
||||
xy: transformed_xy,
|
||||
xy_stride: text_mesh.xy_stride,
|
||||
uv: text_mesh.uv,
|
||||
@@ -287,36 +247,10 @@ function translate_draw_commands(commands) {
|
||||
indices: text_mesh.indices,
|
||||
num_vertices: text_mesh.num_vertices,
|
||||
num_indices: text_mesh.num_indices,
|
||||
size_indices: text_mesh.size_indices,
|
||||
texture_id: font.texture.id
|
||||
size_indices: text_mesh.size_indices
|
||||
}
|
||||
|
||||
renderer_commands.push({
|
||||
op: "geometry_raw",
|
||||
data: transformed_geom
|
||||
})
|
||||
break
|
||||
|
||||
case "draw_slice9":
|
||||
var img = graphics.texture(cmd.image)
|
||||
var gpu = img.gpu
|
||||
if (!gpu) break
|
||||
|
||||
cmd.rect = worldToScreenRect(cmd.rect, camera)
|
||||
|
||||
renderer_commands.push({
|
||||
op: "texture9Grid",
|
||||
data: {
|
||||
texture_id: gpu,
|
||||
src: img.rect,
|
||||
leftWidth: cmd.slice,
|
||||
rightWidth: cmd.slice,
|
||||
topHeight: cmd.slice,
|
||||
bottomHeight: cmd.slice,
|
||||
scale: 1.0,
|
||||
dst: cmd.rect
|
||||
}
|
||||
})
|
||||
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":
|
||||
@@ -326,8 +260,14 @@ function translate_draw_commands(commands) {
|
||||
// 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
|
||||
if (!gpu) continue
|
||||
|
||||
// Transform geometry through camera and send to renderer
|
||||
var geom = geomCmd.geometry
|
||||
@@ -336,39 +276,27 @@ function translate_draw_commands(commands) {
|
||||
var camera_params = [camera.a, camera.c, camera.e, camera.f]
|
||||
var transformed_xy = geometry.transform_xy_blob(geom.xy, camera_params)
|
||||
|
||||
// Create new geometry object with transformed coordinates
|
||||
var transformed_geom = {
|
||||
xy: transformed_xy,
|
||||
xy_stride: geom.xy_stride,
|
||||
uv: geom.uv,
|
||||
uv_stride: geom.uv_stride,
|
||||
color: geom.color,
|
||||
color_stride: geom.color_stride,
|
||||
indices: geom.indices,
|
||||
num_vertices: geom.num_vertices,
|
||||
num_indices: geom.num_indices,
|
||||
size_indices: geom.size_indices,
|
||||
texture_id: gpu
|
||||
}
|
||||
|
||||
renderer_commands.push({
|
||||
op: "geometry_raw",
|
||||
data: transformed_geom
|
||||
})
|
||||
// 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 texture_id
|
||||
var gpu
|
||||
if (cmd.texture_id) {
|
||||
// Use the provided texture ID directly
|
||||
texture_id = 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)
|
||||
var gpu = img.gpu
|
||||
if (!gpu) break
|
||||
texture_id = gpu
|
||||
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
|
||||
@@ -378,25 +306,8 @@ function translate_draw_commands(commands) {
|
||||
var camera_params = [camera.a, camera.c, camera.e, camera.f]
|
||||
var transformed_xy = geometry.transform_xy_blob(geom.xy, camera_params)
|
||||
|
||||
// Create new geometry object with transformed coordinates
|
||||
var transformed_geom = {
|
||||
xy: transformed_xy,
|
||||
xy_stride: geom.xy_stride,
|
||||
uv: geom.uv,
|
||||
uv_stride: geom.uv_stride,
|
||||
color: geom.color,
|
||||
color_stride: geom.color_stride,
|
||||
indices: geom.indices,
|
||||
num_vertices: geom.num_vertices,
|
||||
num_indices: geom.num_indices,
|
||||
size_indices: geom.size_indices,
|
||||
texture_id: texture_id
|
||||
}
|
||||
|
||||
renderer_commands.push({
|
||||
op: "geometry_raw",
|
||||
data: transformed_geom
|
||||
})
|
||||
// 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
|
||||
}
|
||||
})
|
||||
@@ -405,30 +316,80 @@ function translate_draw_commands(commands) {
|
||||
}
|
||||
|
||||
///// input /////
|
||||
var input = use('input')
|
||||
var input_cb
|
||||
var input_rate = 1/60
|
||||
function poll_input() {
|
||||
send(video, {kind:'input', op:'get'}, evs => {
|
||||
for (var ev of evs) {
|
||||
if (ev.type == 'window_pixel_size_changed') {
|
||||
win_size.width = ev.width
|
||||
win_size.height = ev.height
|
||||
var evs = input.get_events()
|
||||
|
||||
// Filter and transform events
|
||||
if (renderer && Array.isArray(evs)) {
|
||||
var filteredEvents = []
|
||||
var wantMouse = imgui.wantmouse()
|
||||
var wantKeys = imgui.wantkeys()
|
||||
|
||||
for (var i = 0; i < evs.length; i++) {
|
||||
var event = evs[i]
|
||||
var shouldInclude = true
|
||||
|
||||
// Filter mouse events if ImGui wants mouse input
|
||||
if (wantMouse && (event.type == 'mouse_motion' ||
|
||||
event.type == 'mouse_button_down' ||
|
||||
event.type == 'mouse_button_up' ||
|
||||
event.type == 'mouse_wheel')) {
|
||||
shouldInclude = false
|
||||
}
|
||||
|
||||
if (ev.type == 'quit')
|
||||
// Filter keyboard events if ImGui wants keyboard input
|
||||
if (wantKeys && (event.type == 'key_down' ||
|
||||
event.type == 'key_up' ||
|
||||
event.type == 'text_input' ||
|
||||
event.type == 'text_editing')) {
|
||||
shouldInclude = false
|
||||
}
|
||||
|
||||
if (shouldInclude) {
|
||||
// Transform mouse coordinates from window to renderer coordinates
|
||||
if (event.pos && (event.type == 'mouse_motion' ||
|
||||
event.type == 'mouse_button_down' ||
|
||||
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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
// Handle window events
|
||||
if (event.type == 'window_pixel_size_changed') {
|
||||
win_size.width = event.width
|
||||
win_size.height = event.height
|
||||
}
|
||||
|
||||
if (event.type == 'quit')
|
||||
$_.stop()
|
||||
|
||||
if (ev.type.includes('key')) {
|
||||
if (ev.key)
|
||||
ev.key = input.keyname(ev.key)
|
||||
if (event.type.includes('key')) {
|
||||
if (event.key)
|
||||
event.key = input.keyname(event.key)
|
||||
}
|
||||
|
||||
if (ev.type.startsWith('mouse_') && ev.pos && ev.pos.y)
|
||||
ev.pos.y = -ev.pos.y + logical.height
|
||||
if (event.type.startsWith('mouse_') && event.pos && event.pos.y)
|
||||
event.pos.y = -event.pos.y + logical.height
|
||||
|
||||
filteredEvents.push(event)
|
||||
}
|
||||
}
|
||||
|
||||
evs = filteredEvents
|
||||
}
|
||||
|
||||
input_cb(evs)
|
||||
})
|
||||
$_.delay(poll_input, input_rate)
|
||||
}
|
||||
|
||||
@@ -440,20 +401,19 @@ prosperon.input = function(fn)
|
||||
|
||||
// 2) helper to build & send a batch, then call done()
|
||||
prosperon.create_batch = function create_batch(draw_cmds, done) {
|
||||
def batch = [
|
||||
{op:'set', prop:'drawColor', value:{r:0.1,g:0.1,b:0.15,a:1}},
|
||||
{op:'clear'}
|
||||
]
|
||||
renderer.drawColor = {r:0.1,g:0.1,b:0.15,a:1}
|
||||
renderer.clear()
|
||||
|
||||
if (draw_cmds && draw_cmds.length)
|
||||
batch.push(...translate_draw_commands(draw_cmds))
|
||||
var commands = translate_draw_commands(draw_cmds)
|
||||
|
||||
batch.push(
|
||||
{op:'set', prop:'drawColor', value:{r:1,g:1,b:1,a:1}},
|
||||
{op:'imgui_render'},
|
||||
{op:'present'}
|
||||
)
|
||||
renderer.drawColor = {r:1,g:1,b:1,a:1}
|
||||
imgui.endframe(renderer)
|
||||
imgui.newframe()
|
||||
|
||||
send(video, {kind:'renderer', op:'batch', data:batch}, done)
|
||||
renderer.present()
|
||||
|
||||
if (done) done()
|
||||
}
|
||||
|
||||
////////// dmon hot reload ////////
|
||||
@@ -492,7 +452,7 @@ prosperon.dmon = function()
|
||||
|
||||
var window_cmds = {
|
||||
size(size) {
|
||||
send(video, {kind: 'window', op:'set', data: {property: 'size', value: size}})
|
||||
window.size = size
|
||||
},
|
||||
}
|
||||
|
||||
@@ -506,7 +466,7 @@ var renderer_cmds = {
|
||||
resolution(e) {
|
||||
logical.width = e.width
|
||||
logical.height = e.height
|
||||
send(video, {kind:"renderer", op:'set', prop:'logicalPresentation', value: {...e}})
|
||||
renderer.logicalPresentation = {...e}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,4 +476,30 @@ prosperon.set_renderer = function(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
|
||||
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
|
||||
|
||||
@@ -108,25 +108,12 @@ function full_upload(buffers) {
|
||||
cmds.submit();
|
||||
}
|
||||
|
||||
full_upload[cell.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[cell.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];
|
||||
@@ -141,13 +128,6 @@ function get_pipeline_ubo_slot(pipeline, name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
get_pipeline_ubo_slot[cell.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 null if not found.
|
||||
`
|
||||
|
||||
function transpose4x4(val) {
|
||||
var out = [];
|
||||
out[0] = val[0]; out[1] = val[4]; out[2] = val[8]; out[3] = val[12];
|
||||
@@ -157,12 +137,6 @@ function transpose4x4(val) {
|
||||
return out;
|
||||
}
|
||||
|
||||
transpose4x4[cell.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++) {
|
||||
@@ -190,14 +164,6 @@ function ubo_obj_to_array(pipeline, name, obj) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
ubo_obj_to_array[cell.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;
|
||||
@@ -209,12 +175,6 @@ function type_to_byte_count(type) {
|
||||
}
|
||||
}
|
||||
|
||||
type_to_byte_count[cell.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]
|
||||
@@ -288,12 +248,6 @@ function make_pipeline(pipeline) {
|
||||
pipeline.gpu = context.make_pipeline(pipeline);
|
||||
}
|
||||
|
||||
make_pipeline[cell.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) {
|
||||
@@ -319,12 +273,6 @@ function make_shader(sh_file) {
|
||||
return shader
|
||||
}
|
||||
|
||||
make_shader[cell.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.
|
||||
`
|
||||
|
||||
var render_queue = [];
|
||||
var hud_queue = [];
|
||||
|
||||
@@ -355,12 +303,6 @@ function upload_model(model) {
|
||||
context.upload(this, bufs);
|
||||
}
|
||||
|
||||
upload_model[cell.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 = [];
|
||||
@@ -373,14 +315,6 @@ function bind_model(pass, pipeline, model) {
|
||||
pass.bind_index_buffer(model.indices);
|
||||
}
|
||||
|
||||
bind_model[cell.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;
|
||||
@@ -396,14 +330,6 @@ function bind_mat(pass, pipeline, mat) {
|
||||
}
|
||||
}
|
||||
|
||||
bind_mat[cell.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++) {
|
||||
@@ -433,13 +359,6 @@ function group_sprites_by_texture(sprites, mesh) {
|
||||
*/
|
||||
}
|
||||
|
||||
group_sprites_by_texture[cell.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",
|
||||
@@ -562,13 +481,6 @@ function render_camera(cmds, camera) {
|
||||
hud_queue = [];
|
||||
}
|
||||
|
||||
render_camera[cell.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 swaps = [];
|
||||
render.present = function() {
|
||||
os.clean_transforms();
|
||||
@@ -602,11 +514,6 @@ render.present = function() {
|
||||
}
|
||||
}
|
||||
|
||||
render.present[cell.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",
|
||||
@@ -637,12 +544,6 @@ function fillmask(ref) {
|
||||
render.draw(shape.quad);
|
||||
}
|
||||
|
||||
render.fillmask[cell.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",
|
||||
@@ -671,36 +572,14 @@ function mask(image, pos, scale, rotation = 0, ref = 1) {
|
||||
render.draw(shape.quad);
|
||||
}
|
||||
|
||||
render.mask[cell.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) {
|
||||
context.viewport(rect);
|
||||
}
|
||||
|
||||
render.viewport[cell.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[cell.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 std_sampler
|
||||
|
||||
if (tracy) tracy.gpu_init()
|
||||
@@ -712,32 +591,16 @@ render.queue = function(cmd) {
|
||||
current_queue.push(cmd)
|
||||
}
|
||||
|
||||
render.queue[cell.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[cell.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[cell.DOC] = `Switch the current queue to the HUD render queue, then invoke 'prosperon.hud' if defined.
|
||||
|
||||
:return: None
|
||||
`
|
||||
|
||||
render.initialize = function(config)
|
||||
{
|
||||
var default_conf = {
|
||||
|
||||
@@ -630,7 +630,7 @@ var replies = {}
|
||||
|
||||
globalThis.send = function send(actor, message, reply) {
|
||||
if (typeof actor != 'object')
|
||||
throw new Error('Must send to an actor object. Provided: ' + actor);
|
||||
throw new Error(`Must send to an actor object. Provided: ${json.encode(actor)}`);
|
||||
|
||||
if (typeof message != 'object')
|
||||
throw new Error('Message must be an object')
|
||||
|
||||
@@ -1,54 +1,18 @@
|
||||
var graphics = this
|
||||
|
||||
graphics[cell.DOC] = `
|
||||
Provides functionality for loading and managing images, fonts, textures, and sprite meshes.
|
||||
Includes both JavaScript and C-implemented routines for creating geometry buffers, performing
|
||||
rectangle packing, etc.
|
||||
`
|
||||
|
||||
var renderer_actor = arg?.[0] || null
|
||||
|
||||
var io = use('io')
|
||||
var time = use('time')
|
||||
var res = use('resources')
|
||||
var json = use('json')
|
||||
var os = use('os')
|
||||
|
||||
var GPU = Symbol()
|
||||
var CPU = Symbol()
|
||||
var LASTUSE = Symbol()
|
||||
var LOADING = Symbol()
|
||||
|
||||
var cache = {}
|
||||
var pending_gpu_loads = []
|
||||
|
||||
graphics.setup = function(renderer)
|
||||
{
|
||||
renderer_actor = renderer
|
||||
|
||||
// Process any pending GPU loads
|
||||
if (renderer_actor && pending_gpu_loads.length > 0) {
|
||||
log.console(`Processing ${pending_gpu_loads.length} pending GPU loads`)
|
||||
for (var img of pending_gpu_loads) {
|
||||
img.loadGPU()
|
||||
}
|
||||
pending_gpu_loads = []
|
||||
}
|
||||
|
||||
// Also process any cached images that need GPU loading
|
||||
if (renderer_actor) {
|
||||
for (var key in cache) {
|
||||
var img = cache[key]
|
||||
if (img instanceof graphics.Image && img.cpu && img.gpu == 0) {
|
||||
img.loadGPU()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Image constructor function
|
||||
// cpu is the surface
|
||||
graphics.Image = function(surfaceData) {
|
||||
// Initialize properties
|
||||
this.cpu = surfaceData || null;
|
||||
this.gpu = 0;
|
||||
this.texture = 0;
|
||||
@@ -58,40 +22,6 @@ graphics.Image = function(surfaceData) {
|
||||
this.rect = {x:0, y:0, width:this.width, height:this.height};
|
||||
this[LOADING] = false;
|
||||
this[LASTUSE] = time.number();
|
||||
|
||||
// Load GPU texture if renderer is available, otherwise queue it
|
||||
if (renderer_actor && this.cpu) {
|
||||
this.loadGPU();
|
||||
} else if (this.cpu) {
|
||||
// Queue for later GPU loading when renderer is available
|
||||
pending_gpu_loads.push(this)
|
||||
}
|
||||
}
|
||||
|
||||
graphics.Image.prototype.loadGPU = function() {
|
||||
if (!this[LOADING] && renderer_actor && this.cpu) {
|
||||
this[LOADING] = true;
|
||||
var self = this;
|
||||
|
||||
send(renderer_actor, {
|
||||
kind: "renderer",
|
||||
op: "loadTexture",
|
||||
data: this.cpu
|
||||
}, function(response) {
|
||||
if (response.error) {
|
||||
log.error("Failed to load texture:")
|
||||
log.error(response.error)
|
||||
self[LOADING] = false;
|
||||
} else {
|
||||
// Store the full response as texture (has width/height)
|
||||
self.texture = response;
|
||||
// Store just the ID as gpu
|
||||
self.gpu = response.id || response;
|
||||
decorate_rect_px(self);
|
||||
self[LOADING] = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add methods to prototype
|
||||
@@ -197,19 +127,13 @@ function create_image(path){
|
||||
return makeAnim(wrapFrames(raw), true);
|
||||
}
|
||||
|
||||
/* ── Case C: ASE helpers returned { animName:{frames,loop}, … } or single frame ── */
|
||||
if(typeof raw == 'object' && !raw.width) {
|
||||
// Check if it's a single surface from ASE (single frame, no tags)
|
||||
if(raw.surface) {
|
||||
if(raw.surface)
|
||||
return new graphics.Image(raw.surface)
|
||||
}
|
||||
|
||||
// Check if it's an untagged animation (multiple frames, no tags)
|
||||
// This happens when ASE has no tags but multiple frames
|
||||
if(raw.frames && Array.isArray(raw.frames) && raw.loop != null)
|
||||
return makeAnim(wrapFrames(raw.frames), !!raw.loop);
|
||||
|
||||
// Multiple named animations from ASE (with tags)
|
||||
def anims = {};
|
||||
for(def [name, anim] of Object.entries(raw)){
|
||||
if(anim && Array.isArray(anim.frames))
|
||||
@@ -240,9 +164,6 @@ image.dimensions = function() {
|
||||
}
|
||||
return [width, height].scale([this.rect[2], this.rect[3]])
|
||||
}
|
||||
image.dimensions[cell.DOC] = `
|
||||
:return: A 2D array [width, height] that is the scaled size of this image (texture size * rect size).
|
||||
`
|
||||
|
||||
var spritesheet
|
||||
var sheet_frames = []
|
||||
@@ -261,10 +182,6 @@ function pack_into_sheet(images) {
|
||||
graphics.is_image = function(obj) {
|
||||
if (obj.texture && obj.rect) return true
|
||||
}
|
||||
graphics.is_image[cell.DOC] = `
|
||||
:param obj: An object to check.
|
||||
:return: True if 'obj' has a .texture and a .rect property, indicating it's an image object.
|
||||
`
|
||||
|
||||
graphics.texture_from_data = function(data)
|
||||
{
|
||||
@@ -278,7 +195,7 @@ graphics.texture_from_data = function(data)
|
||||
return img;
|
||||
}
|
||||
|
||||
graphics.from_surface = function(id, surf)
|
||||
graphics.from_surface = function(surf)
|
||||
{
|
||||
return make_handle(surf)
|
||||
}
|
||||
@@ -388,32 +305,20 @@ graphics.texture = function texture(path) {
|
||||
|
||||
return cached
|
||||
}
|
||||
graphics.texture[cell.DOC] = `
|
||||
:param path: A string path to an image file or an already-loaded image object.
|
||||
:return: An image object with {surface, texture, frames?, etc.} depending on the format.
|
||||
Load or retrieve a cached image, converting it into a GPU texture. If 'path' is already an object, it’s returned directly.
|
||||
`
|
||||
|
||||
graphics.texture.total_size = function() {
|
||||
var size = 0
|
||||
// Not yet implemented, presumably sum of (texture.width * texture.height * 4) for images in RAM
|
||||
return size
|
||||
}
|
||||
graphics.texture.total_size[cell.DOC] = `
|
||||
:return: The total estimated memory size of all cached textures in RAM, in bytes. (Not yet implemented.)
|
||||
`
|
||||
|
||||
graphics.texture.total_vram = function() {
|
||||
var vram = 0
|
||||
// Not yet implemented, presumably sum of GPU memory usage
|
||||
return vram
|
||||
}
|
||||
graphics.texture.total_vram[cell.DOC] = `
|
||||
:return: The total estimated GPU memory usage of all cached textures, in bytes. (Not yet implemented.)
|
||||
`
|
||||
|
||||
graphics.tex_hotreload = function tex_hotreload(file) {
|
||||
// Extract just the filename without path and extension
|
||||
var basename = file.split('/').pop().split('.')[0]
|
||||
|
||||
// Check if this basename exists in our cache
|
||||
@@ -445,17 +350,7 @@ graphics.tex_hotreload = function tex_hotreload(file) {
|
||||
oldimg.rect = {x:0, y:0, width:img.cpu.width, height:img.cpu.height}
|
||||
decorate_rect_px(oldimg)
|
||||
}
|
||||
|
||||
// If the texture was on GPU, trigger reload
|
||||
if (oldGPU && renderer_actor) {
|
||||
oldimg.loadGPU()
|
||||
}
|
||||
}
|
||||
graphics.tex_hotreload[cell.DOC] = `
|
||||
:param file: The file path that was changed on disk.
|
||||
:return: None
|
||||
Reload the image for the given file, updating the cached copy in memory and GPU.
|
||||
`
|
||||
|
||||
/**
|
||||
Merges specific properties from nv into ov, using an array of property names.
|
||||
@@ -494,21 +389,6 @@ graphics.get_font = function get_font(path) {
|
||||
var data = io.slurpbytes(fullpath)
|
||||
var font = graphics.make_font(data, size)
|
||||
|
||||
// Load font texture via renderer actor (async)
|
||||
if (renderer_actor) {
|
||||
send(renderer_actor, {
|
||||
kind: "renderer",
|
||||
op: "loadTexture",
|
||||
data: font.surface
|
||||
}, function(response) {
|
||||
if (response.error) {
|
||||
log.error("Failed to load font texture:", response.error);
|
||||
} else {
|
||||
font.texture = response;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fontcache[fontstr] = font
|
||||
|
||||
return font
|
||||
|
||||
Reference in New Issue
Block a user