render command ops

This commit is contained in:
2025-05-26 00:57:29 -05:00
parent 1141fca63a
commit 45311408d6
5 changed files with 1223 additions and 255 deletions

View File

@@ -88,6 +88,9 @@ $_.receiver(function(msg) {
case 'texture': case 'texture':
response = handle_texture(msg); response = handle_texture(msg);
break; break;
case 'surface':
response = handle_surface(msg);
break;
default: default:
response = {error: "Unknown kind: " + msg.kind}; response = {error: "Unknown kind: " + msg.kind};
} }
@@ -101,65 +104,107 @@ $_.receiver(function(msg) {
// Window operations // Window operations
function handle_window(msg) { function handle_window(msg) {
// Special case: create doesn't need an existing window
if (msg.op === 'create') {
var config = Object.assign({}, default_window, msg.data || {});
var id = allocate_id();
var window = new prosperon.endowments.window(config);
resources.window[id] = window;
return {id: id, data: {size: window.size}};
}
// All other operations require a valid window ID
if (!msg.id || !resources.window[msg.id]) {
return {error: "Invalid window id: " + msg.id};
}
var win = resources.window[msg.id];
switch (msg.op) { switch (msg.op) {
case 'create':
var config = Object.assign({}, default_window, msg.data || {});
var id = allocate_id();
var window = new prosperon.endowments.window(config);
resources.window[id] = window;
return {id: id, data: {width: window.width, height: window.height}};
case 'destroy': case 'destroy':
if (!msg.id || !resources.window[msg.id]) { win.destroy();
return {error: "Invalid window id: " + msg.id};
}
resources.window[msg.id].destroy();
delete resources.window[msg.id]; delete resources.window[msg.id];
return {success: true}; return {success: true};
case 'show': case 'show':
if (!msg.id || !resources.window[msg.id]) { win.visible = true;
return {error: "Invalid window id: " + msg.id};
}
resources.window[msg.id].show();
return {success: true}; return {success: true};
case 'hide': case 'hide':
if (!msg.id || !resources.window[msg.id]) { win.visible = false;
return {error: "Invalid window id: " + msg.id};
}
resources.window[msg.id].hide();
return {success: true}; return {success: true};
case 'set_title': case 'get':
if (!msg.id || !resources.window[msg.id]) { var prop = msg.data ? msg.data.property : null;
return {error: "Invalid window id: " + msg.id}; if (!prop) return {error: "Missing property name"};
// Handle special cases
if (prop === 'surface') {
var surf = win.surface;
if (!surf) return {data: null};
var surf_id = allocate_id();
resources.surface[surf_id] = surf;
return {data: surf_id};
} }
if (!msg.data || !msg.data.title) {
return {error: "Missing title in data"}; return {data: win[prop]};
case 'set':
var prop = msg.data ? msg.data.property : null;
var value = msg.data ? msg.data.value : undefined;
if (!prop) return {error: "Missing property name"};
// Validate property is settable
var readonly = ['id', 'pixelDensity', 'displayScale', 'sizeInPixels', 'flags', 'surface'];
if (readonly.indexOf(prop) !== -1) {
return {error: "Property '" + prop + "' is read-only"};
} }
resources.window[msg.id].title = msg.data.title;
win[prop] = value;
return {success: true}; return {success: true};
case 'get_size': case 'fullscreen':
if (!msg.id || !resources.window[msg.id]) { win.fullscreen();
return {error: "Invalid window id: " + msg.id};
}
var win = resources.window[msg.id];
return {data: {width: win.width, height: win.height}};
case 'set_size':
if (!msg.id || !resources.window[msg.id]) {
return {error: "Invalid window id: " + msg.id};
}
if (!msg.data || typeof msg.data.width !== 'number' || typeof msg.data.height !== 'number') {
return {error: "Missing or invalid width/height in data"};
}
var win = resources.window[msg.id];
win.width = msg.data.width;
win.height = msg.data.height;
return {success: true}; return {success: true};
case 'updateSurface':
win.updateSurface();
return {success: true};
case 'updateSurfaceRects':
if (!msg.data || !msg.data.rects) return {error: "Missing rects array"};
win.updateSurfaceRects(msg.data.rects);
return {success: true};
case 'raise':
win.raise();
return {success: true};
case 'restore':
win.restore();
return {success: true};
case 'flash':
win.flash(msg.data ? msg.data.operation : 'briefly');
return {success: true};
case 'sync':
win.sync();
return {success: true};
case 'setIcon':
if (!msg.data || !msg.data.surface_id) return {error: "Missing surface_id"};
var surf = resources.surface[msg.data.surface_id];
if (!surf) return {error: "Invalid surface id"};
win.set_icon(surf);
return {success: true};
case 'makeRenderer':
var renderer = win.make_renderer();
var renderer_id = allocate_id();
resources.renderer[renderer_id] = renderer;
return {id: renderer_id};
default: default:
return {error: "Unknown window operation: " + msg.op}; return {error: "Unknown window operation: " + msg.op};
} }
@@ -167,41 +212,233 @@ function handle_window(msg) {
// Renderer operations // Renderer operations
function handle_renderer(msg) { function handle_renderer(msg) {
// Special case: createWindowAndRenderer creates both
if (msg.op === 'createWindowAndRenderer') {
var data = msg.data || {};
var result = prosperon.endowments.createWindowAndRenderer(
data.title || "Prosperon Window",
data.width || 640,
data.height || 480,
data.flags || 0
);
var win_id = allocate_id();
var ren_id = allocate_id();
resources.window[win_id] = result.window;
resources.renderer[ren_id] = result.renderer;
return {window_id: win_id, renderer_id: ren_id};
}
// All other operations require a valid renderer ID
if (!msg.id || !resources.renderer[msg.id]) {
return {error: "Invalid renderer id: " + msg.id};
}
var ren = resources.renderer[msg.id];
switch (msg.op) { switch (msg.op) {
case 'create':
if (!msg.data || !msg.data.window_id) {
return {error: "Missing window_id in data"};
}
if (!resources.window[msg.data.window_id]) {
return {error: "Invalid window id: " + msg.data.window_id};
}
var id = allocate_id();
var renderer = prosperon.endowments.renderer.create(resources.window[msg.data.window_id]);
resources.renderer[id] = renderer;
return {id: id};
case 'destroy': case 'destroy':
if (!msg.id || !resources.renderer[msg.id]) {
return {error: "Invalid renderer id: " + msg.id};
}
resources.renderer[msg.id].destroy();
delete resources.renderer[msg.id]; delete resources.renderer[msg.id];
// Renderer is automatically destroyed when all references are gone
return {success: true}; return {success: true};
case 'clear': case 'clear':
if (!msg.id || !resources.renderer[msg.id]) { ren.clear();
return {error: "Invalid renderer id: " + msg.id};
}
resources.renderer[msg.id].clear();
return {success: true}; return {success: true};
case 'present': case 'present':
if (!msg.id || !resources.renderer[msg.id]) { ren.present();
return {error: "Invalid renderer id: " + msg.id};
}
resources.renderer[msg.id].present();
return {success: true}; return {success: true};
case 'flush':
ren.flush();
return {success: true};
case 'get':
var prop = msg.data ? msg.data.property : null;
if (!prop) return {error: "Missing property name"};
// Handle special cases
if (prop === 'window') {
var win = ren.window;
if (!win) return {data: null};
// Find window ID
for (var id in resources.window) {
if (resources.window[id] === win) {
return {data: id};
}
}
// Window not tracked, add it
var win_id = allocate_id();
resources.window[win_id] = win;
return {data: win_id};
}
// Handle special getters that might return objects
if (prop === 'drawColor') {
var color = ren[prop];
if (color && typeof color === 'object') {
// Convert color object to array format [r,g,b,a]
return {data: [color.r || 0, color.g || 0, color.b || 0, color.a || 255]};
}
}
return {data: ren[prop]};
case 'set':
var prop = msg.prop
var value = msg.value
if (!prop) return {error: "Missing property name"};
// Validate property is settable
var readonly = ['window', 'name', 'outputSize', 'currentOutputSize', 'logicalPresentationRect', 'safeArea'];
if (readonly.indexOf(prop) !== -1) {
return {error: "Property '" + prop + "' is read-only"};
}
// Special handling for render target
if (prop === 'target' && value !== null && value !== undefined) {
var tex = resources.texture[value];
if (!tex) return {error: "Invalid texture id"};
value = tex;
}
ren[prop] = value;
return {success: true};
case 'line':
if (!msg.data || !msg.data.points) return {error: "Missing points array"};
ren.line(msg.data.points);
return {success: true};
case 'point':
if (!msg.data || !msg.data.points) return {error: "Missing points"};
ren.point(msg.data.points);
return {success: true};
case 'rect':
if (!msg.data || !msg.data.rect) return {error: "Missing rect"};
ren.rect(msg.data.rect);
return {success: true};
case 'fillRect':
if (!msg.data || !msg.data.rect) return {error: "Missing rect"};
ren.fillRect(msg.data.rect);
return {success: true};
case 'rects':
if (!msg.data || !msg.data.rects) return {error: "Missing rects"};
ren.rects(msg.data.rects);
return {success: true};
case 'lineTo':
if (!msg.data || !msg.data.a || !msg.data.b) return {error: "Missing points a and b"};
ren.lineTo(msg.data.a, msg.data.b);
return {success: true};
case 'texture':
if (!msg.data) return {error: "Missing texture data"};
var tex_id = msg.data.texture_id;
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
ren.texture(
resources.texture[tex_id],
msg.data.src || {x:0, y:0, w:1, h:1},
msg.data.dst || {x:0, y:0, w:100, h:100},
msg.data.angle || 0,
msg.data.anchor || {x:0.5, y:0.5}
);
return {success: true};
case 'sprite':
if (!msg.data || !msg.data.sprite) return {error: "Missing sprite data"};
ren.sprite(msg.data.sprite);
return {success: true};
case 'geometry':
if (!msg.data) return {error: "Missing geometry data"};
var tex_id = msg.data.texture_id;
var tex = tex_id ? resources.texture[tex_id] : null;
ren.geometry(tex, msg.data.geometry);
return {success: true};
case 'debugText':
if (!msg.data || !msg.data.text) return {error: "Missing text"};
ren.debugText(msg.data.pos || {x:0, y:0}, msg.data.text);
return {success: true};
case 'clipEnabled':
return {data: ren.clipEnabled()};
case 'texture9Grid':
if (!msg.data) return {error: "Missing data"};
var tex_id = msg.data.texture_id;
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
ren.texture9Grid(
resources.texture[tex_id],
msg.data.src,
msg.data.leftWidth,
msg.data.rightWidth,
msg.data.topHeight,
msg.data.bottomHeight,
msg.data.scale,
msg.data.dst
);
return {success: true};
case 'textureTiled':
if (!msg.data) return {error: "Missing data"};
var tex_id = msg.data.texture_id;
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
ren.textureTiled(
resources.texture[tex_id],
msg.data.src,
msg.data.scale || 1.0,
msg.data.dst
);
return {success: true};
case 'readPixels':
var surf = ren.readPixels(msg.data ? msg.data.rect : null);
if (!surf) return {error: "Failed to read pixels"};
var surf_id = allocate_id();
resources.surface[surf_id] = surf;
return {id: surf_id};
case 'loadTexture':
if (!msg.data || !msg.data.surface_id) return {error: "Missing surface_id"};
var surf = resources.surface[msg.data.surface_id];
if (!surf) return {error: "Invalid surface id"};
var tex = ren.load_texture(surf);
var tex_id = allocate_id();
resources.texture[tex_id] = tex;
return {id: tex_id};
case 'createTexture':
if (!msg.data || !msg.data.width || !msg.data.height) {
return {error: "Missing width or height"};
}
var tex = ren.createTexture(
msg.data.format || 'rgba8888',
msg.data.access || 'static',
msg.data.width,
msg.data.height
);
if (!tex) return {error: "Failed to create texture"};
var tex_id = allocate_id();
resources.texture[tex_id] = tex;
return {id: tex_id, data: {size: tex.size}};
case 'flush':
ren.flush();
return {success: true};
case 'coordsFromWindow':
if (!msg.data || !msg.data.pos) return {error: "Missing pos"};
return {data: ren.coordsFromWindow(msg.data.pos)};
case 'coordsToWindow':
if (!msg.data || !msg.data.pos) return {error: "Missing pos"};
return {data: ren.coordsToWindow(msg.data.pos)};
default: default:
return {error: "Unknown renderer operation: " + msg.op}; return {error: "Unknown renderer operation: " + msg.op};
} }
@@ -209,12 +446,120 @@ function handle_renderer(msg) {
// Texture operations // Texture operations
function handle_texture(msg) { function handle_texture(msg) {
// Special case: create needs a renderer
if (msg.op === 'create') {
if (!msg.data) return {error: "Missing texture data"};
var ren_id = msg.data.renderer_id;
if (!ren_id || !resources.renderer[ren_id]) return {error: "Invalid renderer id"};
var tex;
var renderer = resources.renderer[ren_id];
// Create from surface
if (msg.data.surface_id) {
var surf = resources.surface[msg.data.surface_id];
if (!surf) return {error: "Invalid surface id"};
tex = new prosperon.endowments.texture(renderer, surf);
}
// Create from properties
else if (msg.data.width && msg.data.height) {
tex = new prosperon.endowments.texture(renderer, {
width: msg.data.width,
height: msg.data.height,
format: msg.data.format || 'rgba8888',
pixels: msg.data.pixels,
pitch: msg.data.pitch
});
}
else {
return {error: "Must provide either surface_id or width/height"};
}
var tex_id = allocate_id();
resources.texture[tex_id] = tex;
return {id: tex_id, data: {size: tex.size}};
}
// All other operations require a valid texture ID
if (!msg.id || !resources.texture[msg.id]) {
return {error: "Invalid texture id: " + msg.id};
}
var tex = resources.texture[msg.id];
switch (msg.op) { switch (msg.op) {
case 'create': case 'destroy':
// TODO: Implement texture creation delete resources.texture[msg.id];
return {error: "Texture operations not yet implemented"}; // Texture is automatically destroyed when all references are gone
return {success: true};
case 'get':
var prop = msg.data ? msg.data.property : null;
if (!prop) return {error: "Missing property name"};
return {data: tex[prop]};
case 'set':
var prop = msg.data ? msg.data.property : null;
var value = msg.data ? msg.data.value : undefined;
if (!prop) return {error: "Missing property name"};
// Validate property is settable
var readonly = ['size', 'width', 'height'];
if (readonly.indexOf(prop) !== -1) {
return {error: "Property '" + prop + "' is read-only"};
}
tex[prop] = value;
return {success: true};
case 'update':
if (!msg.data) return {error: "Missing update data"};
tex.update(
msg.data.rect || null,
msg.data.pixels,
msg.data.pitch || 0
);
return {success: true};
case 'lock':
var result = tex.lock(msg.data ? msg.data.rect : null);
return {data: result};
case 'unlock':
tex.unlock();
return {success: true};
case 'query':
return {data: tex.query()};
default: default:
return {error: "Unknown texture operation: " + msg.op}; return {error: "Unknown texture operation: " + msg.op};
} }
} }
// Surface operations (mainly for cleanup)
function handle_surface(msg) {
switch (msg.op) {
case 'destroy':
if (!msg.id || !resources.surface[msg.id]) {
return {error: "Invalid surface id: " + msg.id};
}
delete resources.surface[msg.id];
return {success: true};
default:
return {error: "Unknown surface operation: " + msg.op};
}
}
// Utility function to create window and renderer
prosperon.endowments = prosperon.endowments || {};
// Export resource info for debugging
prosperon.sdl_video = {
resources: resources,
next_id: function() { return next_id; },
// Export the actor reference for external access
actor: $_
};

View File

@@ -190,7 +190,6 @@ TO = js2##TYPE(JS, val); \
JS_FreeValue(JS, val); } \ JS_FreeValue(JS, val); } \
int js2bool(JSContext *js, JSValue v) { return JS_ToBool(js,v); } int js2bool(JSContext *js, JSValue v) { return JS_ToBool(js,v); }
static inline const char *js2cstring(JSContext *js, JSValue v) { return JS_ToCString(js,v); }
JSValue number2js(JSContext *js, double g) { return JS_NewFloat64(js,g); } JSValue number2js(JSContext *js, double g) { return JS_NewFloat64(js,g); }
double js2number(JSContext *js, JSValue v) { double js2number(JSContext *js, JSValue v) {

View File

@@ -99,7 +99,7 @@ JSValue js_##ID##_get_##ENTRY (JSContext *js, JSValue self) { \
return TYPE##2js(js,js2##ID (js, self)->ENTRY); } \ return TYPE##2js(js,js2##ID (js, self)->ENTRY); } \
#define QJSCLASS(TYPE, ...)\ #define QJSCLASS(TYPE, ...)\
static JSClassID js_##TYPE##_id;\ JSClassID js_##TYPE##_id;\
static void js_##TYPE##_finalizer(JSRuntime *rt, JSValue val){\ static void js_##TYPE##_finalizer(JSRuntime *rt, JSValue val){\
TYPE *n = JS_GetOpaque(val, js_##TYPE##_id);\ TYPE *n = JS_GetOpaque(val, js_##TYPE##_id);\
TracyCFreeN(n, #TYPE); \ TracyCFreeN(n, #TYPE); \

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,28 @@
var video = use('sdl_video') var video = use('sdl_video')
var myid var window
var myrender var renderer
send({__ACTORDATA__:{id:video}}, {kind:"window", op: "create"}, ({id}) => { var act = {__ACTORDATA__:{id:video}}
myid = id
function handle_response(data)
{
if (data.error)
console.log(json.encode(data))
}
send(act, {kind:"window", op: "create"}, ({id}) => {
window = id
console.log(`made window id ${id}`) console.log(`made window id ${id}`)
send(act, {kind:"window", op:"makeRenderer", id: window}, ({id}) => {
renderer = id
console.log(`made renderer with id ${id}`)
send(act, {kind:"renderer", id: renderer, op:"set", prop: "drawColor", value:[0.6,0.4,0.5,1]}, handle_response)
send(act, {kind:"renderer", id: renderer, op:"clear"}, handle_response)
send(act, {kind:"renderer", id: renderer, op: "present"}, handle_response)
})
}) })
$_.delay($_.stop, 2) $_.delay($_.stop, 2)