gpu backend uniform binding

This commit is contained in:
2025-08-01 13:13:54 -05:00
parent 8033c161d0
commit 07595aad63
4 changed files with 201 additions and 80 deletions

View File

@@ -133,7 +133,7 @@ draw.slice9 = function slice9(image, rect = [0,0], slice = 0, info = slice9_info
}) })
} }
draw.image = function image(image, rect, scale = {x:1,y:1}, anchor, shear, info = {mode:"nearest"}, material = {color:{r:1,g:1,b:1,a:1}}) { draw.image = function image(image, rect, scale = {x:1,y:1}, anchor, shear, info, material) {
if (!rect) throw Error('Need rectangle to render image.') if (!rect) throw Error('Need rectangle to render image.')
if (!image) throw Error('Need an image to render.') if (!image) throw Error('Need an image to render.')

View File

@@ -193,32 +193,14 @@ shader_type = 'msl'
var sampler_cache = {} var sampler_cache = {}
function canonicalize_sampler(desc) { function canonicalize_sampler(desc) {
if (desc == true) return json.encode(desc)
return json.encode(default_sampler)
var sampler_obj = {}
sampler_obj.__proto__ = default_sampler
if (typeof desc == 'object') {
for (var key in desc) {
if (desc.hasOwnProperty(key)) {
sampler_obj[key] = desc[key]
}
}
}
var keys = Object.keys(sampler_obj).sort()
var canonical = {}
for (var i = 0; i < keys.length; i++)
canonical[keys[i]] = sampler_obj[keys[i]]
return json.encode(canonical)
} }
function get_sampler(desc) { function get_sampler(desc) {
var key = canonicalize_sampler(desc) var key = canonicalize_sampler(desc)
if (!sampler_cache[key]) { if (!sampler_cache[key]) {
var sampler_config = json.decode(key) var sampler_config = json.decode(key)
sampler_cache[key] = new sdl_gpu.sampler(device, sampler_config) sampler_cache[key] = new sdl_gpu.sampler(device, sampler_config)
} }
@@ -296,7 +278,6 @@ function get_pipeline_for_material(mat = {}) {
cfg.__proto__ = sprite_pipeline cfg.__proto__ = sprite_pipeline
material_pipeline_cache[key] = load_pipeline(cfg) material_pipeline_cache[key] = load_pipeline(cfg)
log.console(`created pipeline for ${json.encode(cfg)}`)
} }
return material_pipeline_cache[key]; return material_pipeline_cache[key];
@@ -304,9 +285,95 @@ function get_pipeline_for_material(mat = {}) {
function load_pipeline(config) function load_pipeline(config)
{ {
config.vertex = make_shader(config.vertex)[GPU] // pull back the JS shader objects (they have `.reflection`)
config.fragment = make_shader(config.fragment)[GPU] def vertShader = make_shader(config.vertex);
return new sdl_gpu.graphics_pipeline(device, config) def fragShader = make_shader(config.fragment);
// build the GPU pipeline
def gpuPipeline = new sdl_gpu.graphics_pipeline(device, {
vertex: vertShader[GPU],
fragment: fragShader[GPU],
// ...all the other config fields...
primitive: config.primitive,
blend: config.blend,
cull: config.cull,
face: config.face,
depth: config.depth,
stencil: config.stencil,
alpha_to_coverage: config.alpha_to_coverage,
multisample: config.multisample,
label: config.label,
target: config.target,
vertex_buffer_descriptions: config.vertex_buffer_descriptions,
vertex_attributes: config.vertex_attributes
});
// stash the reflection in the JS wrapper for easy access later
gpuPipeline._reflection = {
vertex: vertShader.reflection,
fragment: fragShader.reflection
};
return gpuPipeline;
}
// Helper function to pack JavaScript objects into binary blob for UBOs
function pack_ubo(obj, ubo_type, reflection) {
var type_def = reflection.types[ubo_type];
if (!type_def) {
log.console(`Warning: No type definition found for ${ubo_type}`);
return geometry.array_blob([]);
}
var result_blob = new blob();
// Process each member in the UBO structure
for (var member of type_def.members) {
var value = obj[member.name];
if (value == null) {
if (member.type == "vec4") {
result_blob.write_blob(geometry.array_blob([1, 1, 1, 1]));
} else if (member.type == "vec3") {
result_blob.write_blob(geometry.array_blob([1, 1, 1]));
} else if (member.type == "vec2") {
result_blob.write_blob(geometry.array_blob([1, 1]));
} else if (member.type == "float") {
result_blob.write_blob(geometry.array_blob([1]));
}
continue;
}
// Convert value to appropriate format based on type
if (member.type == "vec4") {
if (Array.isArray(value)) {
result_blob.write_blob(geometry.array_blob(value));
} else if (typeof value == "object" && value.r != null) {
// Color object
result_blob.write_blob(geometry.array_blob([value.r, value.g, value.b, value.a || 1]));
} else {
// Single value, expand to vec4
result_blob.write_blob(geometry.array_blob([value, value, value, value]));
}
} else if (member.type == "vec3") {
if (Array.isArray(value)) {
result_blob.write_blob(geometry.array_blob(value));
} else if (typeof value == 'object' && value.r != null)
result_blob.write_blob(geometry.array_blob([value.r, value.g, value.b]));
else
result_blob.write_blob(geometry.array_blob([value, value, value]));
} else if (member.type == "vec2") {
if (Array.isArray(value)) {
result_blob.write_blob(geometry.array_blob(value));
} else {
result_blob.write_blob(geometry.array_blob([value, value]));
}
} else if (member.type == "float") {
result_blob.write_blob(geometry.array_blob([value]));
}
}
return stone(result_blob)
} }
// Initialize ImGui with the window and renderer // Initialize ImGui with the window and renderer
@@ -513,10 +580,7 @@ cmd_fns.camera = function(cmd)
{ {
if (cmd.camera.surface && !cmd.camera.surface[GPU]) { if (cmd.camera.surface && !cmd.camera.surface[GPU]) {
cmd.camera.surface[GPU] = new sdl_gpu.texture(device, cmd.camera.surface) cmd.camera.surface[GPU] = new sdl_gpu.texture(device, cmd.camera.surface)
// Store the sampler description on the texture for later use // Don't store sampler on texture - samplers belong to materials
if (cmd.camera.surface.sampler != null) {
cmd.camera.surface[GPU].sampler_desc = cmd.camera.surface.sampler
}
} }
draw_queue.push(cmd) draw_queue.push(cmd)
} }
@@ -536,15 +600,10 @@ function get_img_gpu(surface)
samples: 0, samples: 0,
type: "2d", type: "2d",
format: "rgba8", format: "rgba8",
sampler: surface.sampler != null ? surface.sampler : true, sampler: surface.sampler != null ? surface.sampler : default_sampler,
color_target: true color_target: true
}) })
// Store the sampler description on the texture for later use
if (surface.sampler != null) {
gpu.sampler_desc = surface.sampler
}
var tbuf = new sdl_gpu.transfer_buffer(device, { var tbuf = new sdl_gpu.transfer_buffer(device, {
size: surface.pixels.length/8, size: surface.pixels.length/8,
usage: "upload" usage: "upload"
@@ -582,7 +641,7 @@ var draw_queue = []
var index_count = 0 var index_count = 0
var vertex_count = 0 var vertex_count = 0
function render_geom(geom, img, pipeline = get_pipeline_for_material(null)) function render_geom(geom, img, pipeline = get_pipeline_for_material(null), material = null)
{ {
if (!img[GPU]) { if (!img[GPU]) {
if (img.surface) if (img.surface)
@@ -601,6 +660,7 @@ function render_geom(geom, img, pipeline = get_pipeline_for_material(null))
draw_queue.push({ draw_queue.push({
pipeline, pipeline,
texture: img[GPU], texture: img[GPU],
material: material,
num_indices: geom.num_indices, num_indices: geom.num_indices,
first_index: index_count, first_index: index_count,
vertex_offset: vertex_count vertex_offset: vertex_count
@@ -625,8 +685,12 @@ cmd_fns.draw_image = function(cmd)
geom.indices = geometry.make_quad_indices(1) geom.indices = geometry.make_quad_indices(1)
geom.num_indices = 6 geom.num_indices = 6
// Ensure material has diffuse property for dynamic binding
if (!cmd.material) cmd.material = {}
if (!cmd.material.diffuse) cmd.material.diffuse = img
var pipeline = get_pipeline_for_material(cmd.material) var pipeline = get_pipeline_for_material(cmd.material)
render_geom(geom, img, pipeline) render_geom(geom, img, pipeline, cmd.material)
} }
cmd_fns.draw_text = function(cmd) cmd_fns.draw_text = function(cmd)
@@ -644,36 +708,47 @@ cmd_fns.draw_text = function(cmd)
font font
) )
// Ensure material has diffuse property for dynamic binding
if (!cmd.material) cmd.material = {}
if (!cmd.material.diffuse) cmd.material.diffuse = font
var pipeline = get_pipeline_for_material(cmd.material) var pipeline = get_pipeline_for_material(cmd.material)
render_geom(mesh, font, pipeline) render_geom(mesh, font, pipeline, cmd.material)
} }
cmd_fns.tilemap = function(cmd) cmd_fns.tilemap = function(cmd)
{ {
var geometryCommands = cmd.tilemap.draw() var geometryCommands = cmd.tilemap.draw()
var pipeline = get_pipeline_for_material(cmd.material)
for (var geomCmd of geometryCommands) { for (var geomCmd of geometryCommands) {
var img = graphics.texture(geomCmd.image) var img = graphics.texture(geomCmd.image)
if (!img) continue if (!img) continue
render_geom(geomCmd.geometry, img, pipeline) // Create a new material for each tile image with diffuse property
var tileMaterial = Object.assign({}, cmd.material || {})
tileMaterial.diffuse = img
var pipeline = get_pipeline_for_material(tileMaterial)
render_geom(geomCmd.geometry, img, pipeline, tileMaterial)
} }
} }
cmd_fns.geometry = function(cmd) cmd_fns.geometry = function(cmd)
{ {
var pipeline = get_pipeline_for_material(cmd.material) var img
if (typeof cmd.image == 'object') { if (typeof cmd.image == 'object') {
render_geom(cmd.geometry, cmd.image, pipeline) img = cmd.image
return } else {
} img = graphics.texture(cmd.image)
var img = graphics.texture(cmd.image)
if (!img) return if (!img) return
}
render_geom(cmd.geometry, img, pipeline) // Ensure material has diffuse property for dynamic binding
if (!cmd.material) cmd.material = {}
if (!cmd.material.diffuse) cmd.material.diffuse = img
var pipeline = get_pipeline_for_material(cmd.material)
render_geom(cmd.geometry, img, pipeline, cmd.material)
} }
cmd_fns.draw_slice9 = function(cmd) cmd_fns.draw_slice9 = function(cmd)
@@ -704,8 +779,12 @@ cmd_fns.draw_slice9 = function(cmd)
var mesh = geometry.slice9(img, cmd.rect, slice_lrtb, slice_info) var mesh = geometry.slice9(img, cmd.rect, slice_lrtb, slice_info)
// Ensure material has diffuse property for dynamic binding
if (!cmd.material) cmd.material = {}
if (!cmd.material.diffuse) cmd.material.diffuse = img
var pipeline = get_pipeline_for_material(cmd.material) var pipeline = get_pipeline_for_material(cmd.material)
render_geom(mesh, img, pipeline) render_geom(mesh, img, pipeline, cmd.material)
} }
cmd_fns.draw_rect = function(cmd) cmd_fns.draw_rect = function(cmd)
@@ -720,7 +799,7 @@ cmd_fns.draw_rect = function(cmd)
white_pixel[GPU] = get_img_gpu(white_pixel) white_pixel[GPU] = get_img_gpu(white_pixel)
var pipeline = get_pipeline_for_material(cmd.material) var pipeline = get_pipeline_for_material(cmd.material)
render_geom(geom, {[GPU]: white_pixel[GPU]}, pipeline) render_geom(geom, {[GPU]: white_pixel[GPU]}, pipeline, cmd.material)
} }
var copy_pass var copy_pass
@@ -829,6 +908,56 @@ prosperon.create_batch = function create_batch(draw_cmds, done) {
current_camera_blob = null current_camera_blob = null
} }
// Dynamic material binding - bind uniforms and textures from material
if (cmd.material && cmd.pipeline._reflection) {
def refl = cmd.pipeline._reflection;
// Bind UBOs (uniform buffer objects)
if (refl.fragment && refl.fragment.ubos) {
for (def ubo of refl.fragment.ubos) {
def name = ubo.name;
def ubo_type = ubo.type;
// For PSConstants or other UBOs, pack the material properties according to the UBO structure
def packed_blob = pack_ubo(cmd.material, ubo_type, refl.fragment);
if (packed_blob && packed_blob.length > 0) {
// Push uniform data to both vertex and fragment stages
// render_queue.push_vertex_uniform_data(ubo.binding, packed_blob);
render_queue.push_fragment_uniform_data(ubo.binding, packed_blob);
}
}
}
// Bind textures for any separate_images
if (refl.fragment && refl.fragment.separate_images) {
for (def imgDesc of refl.fragment.separate_images) {
def name = imgDesc.name;
def binding = imgDesc.binding;
def img = cmd.material[name];
if (img) {
// Ensure texture is on GPU
if (!img[GPU]) {
if (img.surface) {
img[GPU] = get_img_gpu(img.surface);
} else if (img.cpu) {
img[GPU] = get_img_gpu(img.cpu);
}
}
if (img[GPU]) {
// Use material's sampler or default_sampler
def sampler_desc = cmd.material.sampler || default_sampler;
render_pass.bind_samplers(false, binding, [{
texture: img[GPU],
sampler: get_sampler(sampler_desc)
}]);
}
}
}
}
}
// Only bind buffers if not already bound or pipeline changed // Only bind buffers if not already bound or pipeline changed
if (!buffers_bound) { if (!buffers_bound) {
render_pass.bind_buffers(0, [ render_pass.bind_buffers(0, [
@@ -850,12 +979,17 @@ prosperon.create_batch = function create_batch(draw_cmds, done) {
current_camera_blob = cur_cam current_camera_blob = cur_cam
} }
// Use texture's sampler if it has one, otherwise use standard sampler // Bind default texture if material didn't already bind "diffuse"
var sampler_to_use = std_sampler // Always bind the diffuse texture with material's sampler
if (cmd.texture && cmd.texture.sampler_desc) { if (cmd.texture) {
sampler_to_use = get_sampler(cmd.texture.sampler_desc) // Use material's sampler if specified, otherwise use default_sampler
var sampler_desc = (cmd.material && cmd.material.sampler)
? cmd.material.sampler
: default_sampler
var sampler_obj = get_sampler(sampler_desc)
render_pass.bind_samplers(false, 0, [{texture: cmd.texture, sampler: sampler_obj}])
} }
render_pass.bind_samplers(false, 0, [{texture:cmd.texture, sampler: sampler_to_use}])
render_pass.draw_indexed( render_pass.draw_indexed(
cmd.num_indices, cmd.num_indices,

View File

@@ -13,11 +13,13 @@
#define QJSCLASSGPUWRAPPER(WRAPPERTYPE, SDLTYPE) \ #define QJSCLASSGPUWRAPPER(WRAPPERTYPE, SDLTYPE) \
typedef struct { \ typedef struct { \
SDL_GPUDevice *device; \ SDL_GPUDevice *device; \
JSValue js_device; \
SDL_##SDLTYPE *type; \ SDL_##SDLTYPE *type; \
} WRAPPERTYPE; \ } WRAPPERTYPE; \
JSClassID js_SDL_##SDLTYPE##_id; \ JSClassID js_SDL_##SDLTYPE##_id; \
static void js_SDL_##SDLTYPE##_finalizer(JSRuntime *rt, JSValue val) { \ static void js_SDL_##SDLTYPE##_finalizer(JSRuntime *rt, JSValue val) { \
WRAPPERTYPE *wrapper = JS_GetOpaque(val, js_SDL_##SDLTYPE##_id); \ WRAPPERTYPE *wrapper = JS_GetOpaque(val, js_SDL_##SDLTYPE##_id); \
JS_FreeValueRT(rt, wrapper->js_device); \
if (wrapper && wrapper->device && wrapper->type) \ if (wrapper && wrapper->device && wrapper->type) \
SDL_Release##SDLTYPE(wrapper->device, wrapper->type); \ SDL_Release##SDLTYPE(wrapper->device, wrapper->type); \
free(wrapper); \ free(wrapper); \
@@ -31,9 +33,10 @@ SDL_##SDLTYPE *js2SDL_##SDLTYPE(JSContext *js, JSValue val) { \
WRAPPERTYPE *wrapper = JS_GetOpaque(val, js_SDL_##SDLTYPE##_id); \ WRAPPERTYPE *wrapper = JS_GetOpaque(val, js_SDL_##SDLTYPE##_id); \
return wrapper ? wrapper->type : NULL; \ return wrapper ? wrapper->type : NULL; \
} \ } \
JSValue SDL_##SDLTYPE##2js(JSContext *js, SDL_GPUDevice *device, SDL_##SDLTYPE *member) { \ JSValue SDL_##SDLTYPE##2js(JSContext *js, JSValue device, SDL_##SDLTYPE *member) { \
WRAPPERTYPE *wrapper = malloc(sizeof(WRAPPERTYPE)); \ WRAPPERTYPE *wrapper = malloc(sizeof(WRAPPERTYPE)); \
wrapper->device = device; \ wrapper->js_device = JS_DupValue(js,device); \
wrapper->device = js2SDL_GPUDevice(js, device); \
wrapper->type = member; \ wrapper->type = member; \
JSValue j = JS_NewObjectClass(js, js_SDL_##SDLTYPE##_id); \ JSValue j = JS_NewObjectClass(js, js_SDL_##SDLTYPE##_id); \
JS_SetOpaque(j, wrapper); \ JS_SetOpaque(j, wrapper); \
@@ -719,7 +722,7 @@ static JSValue js_gpu_graphics_pipeline_constructor(JSContext *js, JSValueConst
SDL_GPUGraphicsPipeline *pipeline = SDL_CreateGPUGraphicsPipeline(gpu, &info); SDL_GPUGraphicsPipeline *pipeline = SDL_CreateGPUGraphicsPipeline(gpu, &info);
if (!pipeline) return JS_ThrowInternalError(js, "Failed to create GPU pipeline: %s", SDL_GetError()); if (!pipeline) return JS_ThrowInternalError(js, "Failed to create GPU pipeline: %s", SDL_GetError());
return SDL_GPUGraphicsPipeline2js(js, gpu, pipeline); return SDL_GPUGraphicsPipeline2js(js, argv[0], pipeline);
} }
// Standalone sampler constructor: new sdl_gpu.sampler(device, config) // Standalone sampler constructor: new sdl_gpu.sampler(device, config)
@@ -750,7 +753,7 @@ static JSValue js_gpu_sampler_constructor(JSContext *js, JSValueConst self, int
SDL_GPUSampler *sdl_sampler = SDL_CreateGPUSampler(gpu, &info); SDL_GPUSampler *sdl_sampler = SDL_CreateGPUSampler(gpu, &info);
if (!sdl_sampler) return JS_ThrowInternalError(js, "Failed to create GPU sampler: %s", SDL_GetError()); if (!sdl_sampler) return JS_ThrowInternalError(js, "Failed to create GPU sampler: %s", SDL_GetError());
return SDL_GPUSampler2js(js, gpu, sdl_sampler); return SDL_GPUSampler2js(js, argv[0], sdl_sampler);
} }
JSC_CCALL(gpu_driver, JSC_CCALL(gpu_driver,
@@ -807,7 +810,7 @@ static JSValue js_gpu_shader_constructor(JSContext *js, JSValueConst self, int a
if (!shader) if (!shader)
return JS_ThrowReferenceError(js, "Unable to create shader: %s", SDL_GetError()); return JS_ThrowReferenceError(js, "Unable to create shader: %s", SDL_GetError());
return SDL_GPUShader2js(js, gpu, shader); return SDL_GPUShader2js(js, argv[0], shader);
} }
JSC_CCALL(gpu_acquire_cmd_buffer, JSC_CCALL(gpu_acquire_cmd_buffer,
@@ -879,7 +882,7 @@ static JSValue js_gpu_compute_pipeline_constructor(JSContext *js, JSValueConst s
SDL_GPUComputePipeline *pipeline = SDL_CreateGPUComputePipeline(gpu, &info); SDL_GPUComputePipeline *pipeline = SDL_CreateGPUComputePipeline(gpu, &info);
JS_FreeCString(js,info.entrypoint); JS_FreeCString(js,info.entrypoint);
if (!pipeline) return JS_ThrowReferenceError(js,"Could not create compute pipeline: %s", SDL_GetError()); if (!pipeline) return JS_ThrowReferenceError(js,"Could not create compute pipeline: %s", SDL_GetError());
return SDL_GPUComputePipeline2js(js, gpu, pipeline); return SDL_GPUComputePipeline2js(js, argv[0], pipeline);
} }
// Standalone buffer constructor: new sdl_gpu.buffer(device, config) // Standalone buffer constructor: new sdl_gpu.buffer(device, config)
@@ -915,7 +918,7 @@ static JSValue js_gpu_buffer_constructor(JSContext *js, JSValueConst self, int a
SDL_GPUBuffer *buffer = SDL_CreateGPUBuffer(gpu, &info); SDL_GPUBuffer *buffer = SDL_CreateGPUBuffer(gpu, &info);
if (!buffer) return JS_ThrowReferenceError(js, "Unable to create buffer: %s", SDL_GetError()); if (!buffer) return JS_ThrowReferenceError(js, "Unable to create buffer: %s", SDL_GetError());
return SDL_GPUBuffer2js(js, gpu, buffer); return SDL_GPUBuffer2js(js, argv[0], buffer);
} }
static JSValue js_gpu_transfer_buffer_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { static JSValue js_gpu_transfer_buffer_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
@@ -936,7 +939,7 @@ static JSValue js_gpu_transfer_buffer_constructor(JSContext *js, JSValueConst se
SDL_GPUTransferBuffer *buffer = SDL_CreateGPUTransferBuffer(gpu, &info); SDL_GPUTransferBuffer *buffer = SDL_CreateGPUTransferBuffer(gpu, &info);
if (!buffer) return JS_ThrowReferenceError(js, "Unable to create transfer buffer: %s", SDL_GetError()); if (!buffer) return JS_ThrowReferenceError(js, "Unable to create transfer buffer: %s", SDL_GetError());
return SDL_GPUTransferBuffer2js(js, gpu, buffer); return SDL_GPUTransferBuffer2js(js, argv[0], buffer);
} }
// Standalone texture constructor: new sdl_gpu.texture(device, config) // Standalone texture constructor: new sdl_gpu.texture(device, config)
@@ -974,7 +977,7 @@ static JSValue js_gpu_texture_constructor(JSContext *js, JSValueConst self, int
SDL_GPUTexture *tex = SDL_CreateGPUTexture(gpu, &info); SDL_GPUTexture *tex = SDL_CreateGPUTexture(gpu, &info);
if (!tex) return JS_ThrowReferenceError(js, "Unable to create texture: %s", SDL_GetError()); if (!tex) return JS_ThrowReferenceError(js, "Unable to create texture: %s", SDL_GetError());
JSValue jstex = SDL_GPUTexture2js(js, gpu, tex); JSValue jstex = SDL_GPUTexture2js(js, argv[0], tex);
JS_SetPropertyStr(js, jstex, "width", number2js(js, info.width)); JS_SetPropertyStr(js, jstex, "width", number2js(js, info.width));
JS_SetPropertyStr(js, jstex, "height", number2js(js, info.height)); JS_SetPropertyStr(js, jstex, "height", number2js(js, info.height));
JS_SetPropertyStr(js, jstex, "dim", vec22js(js, (HMM_Vec2){info.width, info.height})); JS_SetPropertyStr(js, jstex, "dim", vec22js(js, (HMM_Vec2){info.width, info.height}));

View File

@@ -789,22 +789,6 @@ JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len,
JSValue JS_JSONStringify(JSContext *ctx, JSValueConst obj, JSValue JS_JSONStringify(JSContext *ctx, JSValueConst obj,
JSValueConst replacer, JSValueConst space0); JSValueConst replacer, JSValueConst space0);
typedef enum JSPromiseStateEnum {
JS_PROMISE_PENDING,
JS_PROMISE_FULFILLED,
JS_PROMISE_REJECTED,
} JSPromiseStateEnum;
JSValue JS_NewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs);
JSPromiseStateEnum JS_PromiseState(JSContext *ctx, JSValue promise);
JSValue JS_PromiseResult(JSContext *ctx, JSValue promise);
/* is_handled = TRUE means that the rejection is handled */
typedef void JSHostPromiseRejectionTracker(JSContext *ctx, JSValueConst promise,
JSValueConst reason,
JS_BOOL is_handled, void *opaque);
void JS_SetHostPromiseRejectionTracker(JSRuntime *rt, JSHostPromiseRejectionTracker *cb, void *opaque);
/* return != 0 if the JS code needs to be interrupted */ /* return != 0 if the JS code needs to be interrupted */
typedef int JSInterruptHandler(JSRuntime *rt, void *opaque); typedef int JSInterruptHandler(JSRuntime *rt, void *opaque);
void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque); void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque);