Files
SimpleRayTracing/native/source/Interop/SrtInterop.c
Misaki f1d3dddb9a Change project structure;
Added new c# binding;
2025-12-30 20:54:05 +09:00

1139 lines
30 KiB
C

#include "Interop/SrtInterop.h"
#include "Geometry/AABB.h"
#include "Geometry/Mesh.h"
#include "Geometry/Triangle.h"
#include "Lighting/Light.h"
#include "Lighting/SkyLight.h"
#include "Material/Material.h"
#include "Material/StandardLit.h"
#include "Algorithm/Sobol.h"
#include "Rendering/Camera.h"
#include "Rendering/PostProcessing.h"
#include "Rendering/Renderer.h"
#include "Rendering/Scene.h"
#include "cglm/struct/mat4.h"
#include "cglm/struct/quat.h"
#include "cglm/struct/vec3.h"
#include <math.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
struct srt_scene_t
{
scene_t scene;
};
struct srt_render_job_t
{
// Keep config owned and stable; render_job_t stores a pointer to it.
rendering_config_t config;
render_job_t job;
};
static inline vec2s srt_to_vec2(srt_vec2_t v) { return (vec2s){.raw = {v.x, v.y}}; }
static inline vec3s srt_to_vec3(srt_vec3_t v) { return (vec3s){.raw = {v.x, v.y, v.z}}; }
static inline srt_vec3_t vec3_to_srt(vec3s v) { return (srt_vec3_t){v.x, v.y, v.z}; }
static inline vec3s safe_normalize(vec3s v, vec3s fallback)
{
float n2 = glms_vec3_norm2(v);
if (n2 < 1e-12f)
{
return fallback;
}
return glms_vec3_scale(v, 1.0f / sqrtf(n2));
}
static void free_current_sky(light_collection_t* lights)
{
if (lights == NULL)
{
return;
}
if (lights->sky_light.free_sky_data != NULL)
{
lights->sky_light.free_sky_data(lights->sky_light.data);
}
free(lights->sky_light.data);
lights->sky_light = (sky_light_t){0};
}
static inline versors srt_to_quat(srt_quat_t q) { return (versors){.raw = {q.x, q.y, q.z, q.w}}; }
static inline srt_quat_t quat_to_srt(versors q) { return (srt_quat_t){q.x, q.y, q.z, q.w}; }
static inline mat4s srt_to_mat4(const srt_mat4_t* m)
{
if (m == NULL)
{
return glms_mat4_identity();
}
// cglm expects column-major float[16]
return glms_mat4_make(m->m);
}
static srt_result_e set_material_properties_blob(material_t* m, const void* properties, uint32_t size)
{
if (m == NULL)
{
return SRT_INVALID_ARGUMENT;
}
if (properties == NULL && size != 0)
{
return SRT_INVALID_ARGUMENT;
}
void* new_mem = NULL;
if (size != 0)
{
new_mem = malloc(size);
if (new_mem == NULL)
{
return SRT_ERROR;
}
memcpy(new_mem, properties, size);
}
free(m->properties);
m->properties = new_mem;
m->properties_size = (size_t)size;
return SRT_OK;
}
static inline vec3s mat4_mul_point(mat4s m, vec3s p)
{
return glms_mat4_mulv3(m, p, 1.0f);
}
static inline aabb_t aabb_transform_local(mat4s m, aabb_t aabb)
{
vec3s c000 = mat4_mul_point(m, (vec3s){aabb.min.x, aabb.min.y, aabb.min.z});
vec3s c001 = mat4_mul_point(m, (vec3s){aabb.min.x, aabb.min.y, aabb.max.z});
vec3s c010 = mat4_mul_point(m, (vec3s){aabb.min.x, aabb.max.y, aabb.min.z});
vec3s c011 = mat4_mul_point(m, (vec3s){aabb.min.x, aabb.max.y, aabb.max.z});
vec3s c100 = mat4_mul_point(m, (vec3s){aabb.max.x, aabb.min.y, aabb.min.z});
vec3s c101 = mat4_mul_point(m, (vec3s){aabb.max.x, aabb.min.y, aabb.max.z});
vec3s c110 = mat4_mul_point(m, (vec3s){aabb.max.x, aabb.max.y, aabb.min.z});
vec3s c111 = mat4_mul_point(m, (vec3s){aabb.max.x, aabb.max.y, aabb.max.z});
aabb_t out = invalid_aabb();
aabb_growth(&out, c000);
aabb_growth(&out, c001);
aabb_growth(&out, c010);
aabb_growth(&out, c011);
aabb_growth(&out, c100);
aabb_growth(&out, c101);
aabb_growth(&out, c110);
aabb_growth(&out, c111);
return out;
}
static void scene_update_instance_bounds_for_model(scene_t* scene, uint32_t model_id)
{
if (scene == NULL)
{
return;
}
if (model_id >= scene->mesh_models.capacity || !scene->mesh_models.buffer[model_id].active)
{
return;
}
aabb_t local_bounds = scene->mesh_models.buffer[model_id].local_bounds;
for (uint32_t i = 0; i < scene->mesh_instances.capacity; ++i)
{
mesh_instance_t* inst = &scene->mesh_instances.buffer[i];
if (!inst->active)
{
continue;
}
if (inst->model_id != model_id)
{
continue;
}
inst->world_bounds = aabb_transform_local(inst->local_to_world, local_bounds);
}
}
SRT_API srt_scene_t* srt_scene_create(uint64_t triangle_count,
uint16_t texture_count,
uint8_t material_count,
uint32_t punctual_light_count)
{
srt_scene_t* h = (srt_scene_t*)calloc(1, sizeof(srt_scene_t));
if (h == NULL)
{
return NULL;
}
if (!scene_init(&h->scene, triangle_count, texture_count, material_count, punctual_light_count))
{
free(h);
return NULL;
}
return h;
}
SRT_API void srt_scene_destroy(srt_scene_t* scene)
{
if (scene == NULL)
{
return;
}
scene_free(&scene->scene);
free(scene);
}
SRT_API srt_result_e srt_scene_commit(srt_scene_t* scene)
{
if (scene == NULL)
{
return SRT_INVALID_ARGUMENT;
}
return scene_build_bvh(&scene->scene) ? SRT_OK : SRT_ERROR;
}
SRT_API srt_result_e srt_scene_camera_get(const srt_scene_t* scene, srt_camera_params_t* out_params)
{
if (scene == NULL || out_params == NULL)
{
return SRT_INVALID_ARGUMENT;
}
const camera_t* c = &scene->scene.camera;
float aspect = 1.0f;
if (c->size_y != 0.0f)
{
aspect = c->size_x / c->size_y;
}
*out_params = (srt_camera_params_t){
.position = vec3_to_srt(c->position),
.rotation = quat_to_srt(c->rotation),
.focal_length = c->focal_length,
.size_x = c->size_x,
.aspect_ratio = aspect,
};
return SRT_OK;
}
SRT_API srt_result_e srt_scene_camera_set(srt_scene_t* scene, const srt_camera_params_t* params)
{
if (scene == NULL || params == NULL)
{
return SRT_INVALID_ARGUMENT;
}
float aspect = params->aspect_ratio;
if (!(aspect > 0.0f) || isinf(aspect) || isnan(aspect))
{
aspect = 1.0f;
}
scene->scene.camera = camera_create(
srt_to_vec3(params->position),
srt_to_quat(params->rotation),
params->focal_length,
params->size_x,
aspect
);
return SRT_OK;
}
/* ---------------- Lights ---------------- */
SRT_API srt_result_e srt_scene_get_directional_light_count(const srt_scene_t* scene, uint32_t* out_count)
{
if (scene == NULL || out_count == NULL)
{
return SRT_INVALID_ARGUMENT;
}
*out_count = scene->scene.lights.directional_light_count;
return SRT_OK;
}
SRT_API srt_result_e srt_scene_get_directional_light(const srt_scene_t* scene, uint32_t id, srt_directional_light_t* out_light)
{
if (scene == NULL || out_light == NULL)
{
return SRT_INVALID_ARGUMENT;
}
const light_collection_t* L = &scene->scene.lights;
if (id >= L->directional_light_count)
{
return SRT_OUT_OF_RANGE;
}
const directional_light_t* src = &L->directional_lights[id];
*out_light = (srt_directional_light_t){
.direction = vec3_to_srt(src->direction),
.color = vec3_to_srt(src->color),
.intensity = src->intensity,
.angular_diameter = src->angular_diameter,
};
return SRT_OK;
}
SRT_API srt_result_e srt_scene_add_directional_light(srt_scene_t* scene, const srt_directional_light_t* light, uint32_t* out_id)
{
if (scene == NULL || light == NULL || out_id == NULL)
{
return SRT_INVALID_ARGUMENT;
}
light_collection_t* L = &scene->scene.lights;
if (L->directional_light_count >= L->max_directional_lights)
{
return SRT_OUT_OF_CAPACITY;
}
light_entity_t e = light_create_directional_light(L);
uint32_t id = e.id;
directional_light_t* dst = &L->directional_lights[id];
dst->direction = safe_normalize(srt_to_vec3(light->direction), (vec3s){.raw = {0.0f, 1.0f, 0.0f}});
dst->color = srt_to_vec3(light->color);
dst->intensity = light->intensity;
dst->angular_diameter = light->angular_diameter;
*out_id = id;
return SRT_OK;
}
SRT_API srt_result_e srt_scene_set_directional_light(srt_scene_t* scene, uint32_t id, const srt_directional_light_t* light)
{
if (scene == NULL || light == NULL)
{
return SRT_INVALID_ARGUMENT;
}
light_collection_t* L = &scene->scene.lights;
if (id >= L->directional_light_count)
{
return SRT_OUT_OF_RANGE;
}
directional_light_t* dst = &L->directional_lights[id];
dst->direction = safe_normalize(srt_to_vec3(light->direction), (vec3s){.raw = {0.0f, 1.0f, 0.0f}});
dst->color = srt_to_vec3(light->color);
dst->intensity = light->intensity;
dst->angular_diameter = light->angular_diameter;
return SRT_OK;
}
SRT_API srt_result_e srt_scene_remove_directional_light(srt_scene_t* scene, uint32_t id)
{
if (scene == NULL)
{
return SRT_INVALID_ARGUMENT;
}
light_collection_t* L = &scene->scene.lights;
if (id >= L->directional_light_count)
{
return SRT_OUT_OF_RANGE;
}
uint32_t last = L->directional_light_count - 1;
if (id != last)
{
L->directional_lights[id] = L->directional_lights[last];
}
L->directional_light_count--;
return SRT_OK;
}
SRT_API srt_result_e srt_scene_get_punctual_light_count(const srt_scene_t* scene, uint32_t* out_count)
{
if (scene == NULL || out_count == NULL)
{
return SRT_INVALID_ARGUMENT;
}
*out_count = scene->scene.lights.punctual_light_count;
return SRT_OK;
}
SRT_API srt_result_e srt_scene_get_punctual_light(const srt_scene_t* scene, uint32_t id, srt_punctual_light_t* out_light)
{
if (scene == NULL || out_light == NULL)
{
return SRT_INVALID_ARGUMENT;
}
const light_collection_t* L = &scene->scene.lights;
if (id >= L->punctual_light_count)
{
return SRT_OUT_OF_RANGE;
}
const punctual_light_t* src = &L->punctual_lights[id];
*out_light = (srt_punctual_light_t){
.position = vec3_to_srt(src->position),
.color = vec3_to_srt(src->color),
.intensity = src->intensity,
.type = (uint32_t)src->type,
};
return SRT_OK;
}
SRT_API srt_result_e srt_scene_add_punctual_light(srt_scene_t* scene, const srt_punctual_light_t* light, uint32_t* out_id)
{
if (scene == NULL || light == NULL || out_id == NULL)
{
return SRT_INVALID_ARGUMENT;
}
light_collection_t* L = &scene->scene.lights;
if (L->punctual_light_count >= L->max_punctual_lights)
{
return SRT_OUT_OF_CAPACITY;
}
light_entity_t e = light_create_punctual_light(L);
uint32_t id = e.id;
punctual_light_t* dst = &L->punctual_lights[id];
dst->position = srt_to_vec3(light->position);
dst->color = srt_to_vec3(light->color);
dst->intensity = light->intensity;
dst->type = (punctual_light_type_e)light->type;
*out_id = id;
return SRT_OK;
}
SRT_API srt_result_e srt_scene_set_punctual_light(srt_scene_t* scene, uint32_t id, const srt_punctual_light_t* light)
{
if (scene == NULL || light == NULL)
{
return SRT_INVALID_ARGUMENT;
}
light_collection_t* L = &scene->scene.lights;
if (id >= L->punctual_light_count)
{
return SRT_OUT_OF_RANGE;
}
punctual_light_t* dst = &L->punctual_lights[id];
dst->position = srt_to_vec3(light->position);
dst->color = srt_to_vec3(light->color);
dst->intensity = light->intensity;
dst->type = (punctual_light_type_e)light->type;
return SRT_OK;
}
SRT_API srt_result_e srt_scene_remove_punctual_light(srt_scene_t* scene, uint32_t id)
{
if (scene == NULL)
{
return SRT_INVALID_ARGUMENT;
}
light_collection_t* L = &scene->scene.lights;
if (id >= L->punctual_light_count)
{
return SRT_OUT_OF_RANGE;
}
uint32_t last = L->punctual_light_count - 1;
if (id != last)
{
L->punctual_lights[id] = L->punctual_lights[last];
}
L->punctual_light_count--;
return SRT_OK;
}
/* ---------------- Materials ---------------- */
SRT_API srt_result_e srt_scene_get_material_count(const srt_scene_t* scene, uint32_t* out_count)
{
if (scene == NULL || out_count == NULL)
{
return SRT_INVALID_ARGUMENT;
}
*out_count = scene->scene.materials.count;
return SRT_OK;
}
SRT_API srt_result_e srt_scene_get_material_emission(const srt_scene_t* scene, srt_material_handle_t mat, srt_vec3_t* out_emission)
{
if (scene == NULL || out_emission == NULL)
{
return SRT_INVALID_ARGUMENT;
}
const material_collection_t* mats = &scene->scene.materials;
if (mat.id == INVALID_MATERIAL_ID || mat.id >= mats->count)
{
return SRT_OUT_OF_RANGE;
}
*out_emission = vec3_to_srt(mats->buffer[mat.id].emission);
return SRT_OK;
}
SRT_API srt_result_e srt_scene_set_material_emission(srt_scene_t* scene, srt_material_handle_t mat, srt_vec3_t emission)
{
if (scene == NULL)
{
return SRT_INVALID_ARGUMENT;
}
material_collection_t* mats = &scene->scene.materials;
if (mat.id == INVALID_MATERIAL_ID || mat.id >= mats->count)
{
return SRT_OUT_OF_RANGE;
}
mats->buffer[mat.id].emission = srt_to_vec3(emission);
return SRT_OK;
}
SRT_API srt_result_e srt_scene_get_material_properties_size(const srt_scene_t* scene, srt_material_handle_t mat, uint32_t* out_size)
{
if (scene == NULL || out_size == NULL)
{
return SRT_INVALID_ARGUMENT;
}
const material_collection_t* mats = &scene->scene.materials;
if (mat.id == INVALID_MATERIAL_ID || mat.id >= mats->count)
{
return SRT_OUT_OF_RANGE;
}
*out_size = (uint32_t)mats->buffer[mat.id].properties_size;
return SRT_OK;
}
SRT_API srt_result_e srt_scene_get_material_properties(const srt_scene_t* scene,
srt_material_handle_t mat,
void* out_properties,
uint32_t out_properties_size,
uint32_t* out_required_size)
{
if (scene == NULL || out_required_size == NULL)
{
return SRT_INVALID_ARGUMENT;
}
const material_collection_t* mats = &scene->scene.materials;
if (mat.id == INVALID_MATERIAL_ID || mat.id >= mats->count)
{
return SRT_OUT_OF_RANGE;
}
const material_t* m = &mats->buffer[mat.id];
*out_required_size = (uint32_t)m->properties_size;
if (out_properties == NULL || out_properties_size < (uint32_t)m->properties_size)
{
return SRT_OUT_OF_CAPACITY;
}
if (m->properties_size != 0 && m->properties != NULL)
{
memcpy(out_properties, m->properties, m->properties_size);
}
return SRT_OK;
}
SRT_API srt_result_e srt_scene_set_material_properties(srt_scene_t* scene, srt_material_handle_t mat, const void* properties, uint32_t properties_size)
{
if (scene == NULL)
{
return SRT_INVALID_ARGUMENT;
}
material_collection_t* mats = &scene->scene.materials;
if (mat.id == INVALID_MATERIAL_ID || mat.id >= mats->count)
{
return SRT_OUT_OF_RANGE;
}
return set_material_properties_blob(&mats->buffer[mat.id], properties, properties_size);
}
SRT_API srt_result_e srt_scene_create_standard_lit_material(srt_scene_t* scene,
const srt_standard_lit_properties_t* props,
srt_vec3_t emission,
srt_material_handle_t* out_mat)
{
if (scene == NULL || props == NULL || out_mat == NULL)
{
return SRT_INVALID_ARGUMENT;
}
standard_lit_properties_t p = {0};
p.albedo = srt_to_vec3(props->albedo);
p.diffuse_roughness = props->diffuse_roughness;
p.roughness = props->roughness;
p.metallic = props->metallic;
p.albedo_texture.id = props->albedo_texture.id;
p.normal_texture.id = props->normal_texture.id;
p.roughness_texture.id = props->roughness_texture.id;
p.metallic_texture.id = props->metallic_texture.id;
material_handle_t mh = material_create_standard_lit_default(&p, &scene->scene.materials);
if (mh.id == INVALID_MATERIAL_ID)
{
return SRT_ERROR;
}
scene->scene.materials.buffer[mh.id].emission = srt_to_vec3(emission);
out_mat->id = mh.id;
return SRT_OK;
}
/* ---------------- Textures ---------------- */
SRT_API srt_result_e srt_scene_texture_load(srt_scene_t* scene,
const char* filename,
uint8_t srgb,
uint8_t mipmap,
uint32_t stride,
srt_texture_handle_t* out_texture)
{
if (scene == NULL || filename == NULL || out_texture == NULL)
{
return SRT_INVALID_ARGUMENT;
}
texture_handle_t h = texture_load(filename, srgb != 0, mipmap != 0, (stride_t)stride, &scene->scene.textures);
if (!is_texture_entity_valid(h))
{
return SRT_ERROR;
}
*out_texture = (srt_texture_handle_t){.id = h.id};
return SRT_OK;
}
SRT_API srt_result_e srt_scene_texture_set_sampler(srt_scene_t* scene,
srt_texture_handle_t texture,
uint32_t wrap_mode,
uint32_t filter_mode)
{
if (scene == NULL)
{
return SRT_INVALID_ARGUMENT;
}
texture_handle_t h = {.id = texture.id};
texture_t* tex = get_texture(&scene->scene.textures, h);
if (tex == NULL)
{
return SRT_NOT_FOUND;
}
tex->wrap_mode = (wrap_mode_t)wrap_mode;
tex->filter_mode = (filter_mode_t)filter_mode;
return SRT_OK;
}
/* ---------------- Sky ---------------- */
SRT_API srt_result_e srt_scene_set_sky_none(srt_scene_t* scene)
{
if (scene == NULL)
{
return SRT_INVALID_ARGUMENT;
}
free_current_sky(&scene->scene.lights);
return SRT_OK;
}
SRT_API srt_result_e srt_scene_set_sky_constant(srt_scene_t* scene, srt_vec3_t color, float intensity)
{
if (scene == NULL)
{
return SRT_INVALID_ARGUMENT;
}
free_current_sky(&scene->scene.lights);
constant_sky_data_t data = {
.color = srt_to_vec3(color),
.intensity = intensity,
};
scene->scene.lights.sky_light = sky_create_constant_sky(&data);
return SRT_OK;
}
SRT_API srt_result_e srt_scene_set_sky_hdr(srt_scene_t* scene, srt_texture_handle_t hdri, float intensity)
{
if (scene == NULL)
{
return SRT_INVALID_ARGUMENT;
}
texture_handle_t h = {.id = hdri.id};
if (get_texture(&scene->scene.textures, h) == NULL)
{
return SRT_NOT_FOUND;
}
free_current_sky(&scene->scene.lights);
scene->scene.lights.sky_light = sky_create_hdr_sky(&scene->scene.textures, h, intensity);
return SRT_OK;
}
/* ---------------- Mesh loading (Assimp) ---------------- */
SRT_API srt_result_e srt_scene_load_mesh(srt_scene_t* scene, const char* filename_utf8, srt_mesh_handle_t* out_mesh)
{
if (scene == NULL || filename_utf8 == NULL || out_mesh == NULL)
{
return SRT_INVALID_ARGUMENT;
}
mesh_handle_t mh = mesh_load(filename_utf8, &scene->scene);
*out_mesh = (srt_mesh_handle_t){
.model_id = mh.model_id,
.instance_id = mh.instance_id,
.triangle_id = mh.triangle_id,
.triangle_count = mh.triangle_count,
.material_id = mh.material_id,
.material_count = mh.material_count,
};
if (mh.model_id == UINT32_MAX)
{
return SRT_ERROR;
}
return SRT_OK;
}
/* ---------------- Editor / procedural meshes ---------------- */
SRT_API srt_result_e srt_scene_add_mesh_model(srt_scene_t* scene, uint64_t triangle_reserve, uint32_t* out_model_id)
{
if (scene == NULL || out_model_id == NULL)
{
return SRT_INVALID_ARGUMENT;
}
uint32_t id = scene_add_mesh_model(&scene->scene, triangle_reserve);
if (id == UINT32_MAX)
{
return SRT_ERROR;
}
*out_model_id = id;
return SRT_OK;
}
SRT_API srt_result_e srt_scene_remove_mesh_model(srt_scene_t* scene, uint32_t model_id)
{
if (scene == NULL)
{
return SRT_INVALID_ARGUMENT;
}
if (model_id >= scene->scene.mesh_models.capacity)
{
return SRT_OUT_OF_RANGE;
}
mesh_model_t* model = &scene->scene.mesh_models.buffer[model_id];
if (!model->active)
{
return SRT_NOT_FOUND;
}
// Deactivate instances referencing this model.
for (uint32_t i = 0; i < scene->scene.mesh_instances.capacity; ++i)
{
mesh_instance_t* inst = &scene->scene.mesh_instances.buffer[i];
if (inst->active && inst->model_id == model_id)
{
inst->active = false;
}
}
bvh_tree_free(&model->blas);
triangle_collection_free(&model->triangles);
*model = (mesh_model_t){0};
scene->scene.tlas_dirty = true;
return SRT_OK;
}
SRT_API srt_result_e srt_scene_mesh_model_set_triangles(srt_scene_t* scene,
uint32_t model_id,
const srt_triangle_t* triangles,
uint32_t triangle_count)
{
if (scene == NULL)
{
return SRT_INVALID_ARGUMENT;
}
if (model_id >= scene->scene.mesh_models.capacity)
{
return SRT_OUT_OF_RANGE;
}
mesh_model_t* model = &scene->scene.mesh_models.buffer[model_id];
if (!model->active)
{
return SRT_NOT_FOUND;
}
// Reset triangles
triangle_collection_free(&model->triangles);
if (!triangle_collection_init((size_t)(triangle_count > 0 ? triangle_count : 1), &model->triangles))
{
return SRT_ERROR;
}
model->local_bounds = invalid_aabb();
for (uint32_t i = 0; i < triangle_count; ++i)
{
const srt_triangle_t* t = &triangles[i];
vertex_t v0 = {
.position = srt_to_vec3(t->v0.position),
.normal = srt_to_vec3(t->v0.normal),
.tangent = srt_to_vec3(t->v0.tangent),
.color = srt_to_vec3(t->v0.color),
.uv = srt_to_vec2(t->v0.uv),
};
vertex_t v1 = {
.position = srt_to_vec3(t->v1.position),
.normal = srt_to_vec3(t->v1.normal),
.tangent = srt_to_vec3(t->v1.tangent),
.color = srt_to_vec3(t->v1.color),
.uv = srt_to_vec2(t->v1.uv),
};
vertex_t v2 = {
.position = srt_to_vec3(t->v2.position),
.normal = srt_to_vec3(t->v2.normal),
.tangent = srt_to_vec3(t->v2.tangent),
.color = srt_to_vec3(t->v2.color),
.uv = srt_to_vec2(t->v2.uv),
};
uint8_t mat_id = (uint8_t)(t->material_id > 255 ? 255 : t->material_id);
triangle_create(v0, v1, v2, mat_id, &model->triangles);
}
// Rebuild BLAS and update local bounds.
bvh_tree_free(&model->blas);
if (model->triangles.count > 0)
{
if (bvh_tree_init(&model->blas, &model->triangles))
{
(void)bvh_tree_build(&model->blas);
if (model->blas.nodes != NULL && model->blas.node_count > 0)
{
model->local_bounds = model->blas.nodes[0].bounds;
}
}
}
// Instances need updated world bounds because TLAS rebuild uses cached instance bounds.
scene_update_instance_bounds_for_model(&scene->scene, model_id);
scene->scene.tlas_dirty = true;
return SRT_OK;
}
SRT_API srt_result_e srt_scene_add_mesh_instance(srt_scene_t* scene, uint32_t model_id, const srt_mat4_t* local_to_world, uint32_t* out_instance_id)
{
if (scene == NULL || out_instance_id == NULL)
{
return SRT_INVALID_ARGUMENT;
}
uint32_t id = scene_add_mesh_instance(&scene->scene, model_id, srt_to_mat4(local_to_world));
if (id == UINT32_MAX)
{
return SRT_ERROR;
}
*out_instance_id = id;
return SRT_OK;
}
SRT_API srt_result_e srt_scene_remove_mesh_instance(srt_scene_t* scene, uint32_t instance_id)
{
if (scene == NULL)
{
return SRT_INVALID_ARGUMENT;
}
scene_remove_mesh_instance(&scene->scene, instance_id);
return SRT_OK;
}
SRT_API srt_result_e srt_scene_set_mesh_instance_transform(srt_scene_t* scene, uint32_t instance_id, const srt_mat4_t* local_to_world)
{
if (scene == NULL)
{
return SRT_INVALID_ARGUMENT;
}
scene_set_mesh_instance_transform(&scene->scene, instance_id, srt_to_mat4(local_to_world));
return SRT_OK;
}
/* ---------------- Renderer/job interop ---------------- */
static void ensure_sobol_initialized_once(void)
{
static bool initialized = false;
if (!initialized)
{
sobol_init();
initialized = true;
}
}
SRT_API srt_result_e srt_render_job_create(srt_scene_t* scene,
const srt_rendering_config_t* config,
uint32_t aov_flags,
uint32_t rendering_mode,
uint32_t seed,
srt_render_job_t** out_job)
{
if (scene == NULL || config == NULL || out_job == NULL)
{
return SRT_INVALID_ARGUMENT;
}
if (config->width == 0 || config->height == 0)
{
return SRT_INVALID_ARGUMENT;
}
srt_render_job_t* h = (srt_render_job_t*)calloc(1, sizeof(srt_render_job_t));
if (h == NULL)
{
return SRT_ERROR;
}
h->config = (rendering_config_t){
.width = config->width,
.height = config->height,
.sample_count = config->sample_count,
.max_depth = config->max_depth,
.bucket_size = config->bucket_size,
};
if (h->config.bucket_size == 0)
{
h->config.bucket_size = 64;
}
if (h->config.sample_count == 0)
{
h->config.sample_count = 1;
}
h->job = (render_job_t){
.scene = &scene->scene,
.aov_target = NULL,
.config = &h->config,
.rendering_mode = (rendering_mode_t)rendering_mode,
.aov_flags = (aov_flags_t)aov_flags,
.is_done = false,
};
if (!renderer_aov_target_init(&h->job, h->job.aov_flags))
{
free(h);
return SRT_ERROR;
}
ensure_sobol_initialized_once();
if (seed == 0)
{
seed = (uint32_t)time(NULL);
}
srand(seed);
*out_job = h;
return SRT_OK;
}
SRT_API void srt_render_job_destroy(srt_render_job_t* job)
{
if (job == NULL)
{
return;
}
render_job_free(&job->job);
free(job);
}
SRT_API srt_result_e srt_render_job_start(srt_render_job_t* job)
{
if (job == NULL)
{
return SRT_INVALID_ARGUMENT;
}
renderer_start(&job->job);
return SRT_OK;
}
SRT_API void srt_render_job_request_stop(srt_render_job_t* job)
{
if (job == NULL)
{
return;
}
job->job.is_done = true;
}
SRT_API srt_result_e srt_render_job_is_done(const srt_render_job_t* job, bool* out_done)
{
if (job == NULL || out_done == NULL)
{
return SRT_INVALID_ARGUMENT;
}
*out_done = job->job.is_done;
return SRT_OK;
}
static render_target_t* get_aov_target_checked(const srt_render_job_t* job, uint32_t aov_index)
{
if (job == NULL || job->job.aov_target == NULL)
{
return NULL;
}
if (aov_index >= MAX_AOV_TARGET)
{
return NULL;
}
return job->job.aov_target[aov_index];
}
SRT_API srt_result_e srt_render_job_get_aov_desc(const srt_render_job_t* job,
uint32_t aov_index,
uint32_t* out_width,
uint32_t* out_height,
uint32_t* out_stride_bytes)
{
if (job == NULL || out_width == NULL || out_height == NULL || out_stride_bytes == NULL)
{
return SRT_INVALID_ARGUMENT;
}
render_target_t* rt = get_aov_target_checked(job, aov_index);
if (rt == NULL || rt->buffer == NULL)
{
return SRT_NOT_FOUND;
}
*out_width = rt->width;
*out_height = rt->height;
*out_stride_bytes = rt->width * (uint32_t)sizeof(vec4s);
return SRT_OK;
}
SRT_API const srt_float4_t* srt_render_job_get_aov_pixels(const srt_render_job_t* job, uint32_t aov_index)
{
render_target_t* rt = get_aov_target_checked(job, aov_index);
if (rt == NULL || rt->buffer == NULL)
{
return NULL;
}
return (const srt_float4_t*)rt->buffer;
}
SRT_API srt_result_e srt_render_job_copy_aov_bgra8(const srt_render_job_t* job,
uint32_t aov_index,
uint8_t* dst,
uint32_t dst_stride_bytes)
{
if (job == NULL || dst == NULL)
{
return SRT_INVALID_ARGUMENT;
}
render_target_t* rt = get_aov_target_checked(job, aov_index);
if (rt == NULL || rt->buffer == NULL)
{
return SRT_NOT_FOUND;
}
uint32_t width = rt->width;
uint32_t height = rt->height;
uint32_t required_stride = width * 4u;
if (dst_stride_bytes < required_stride)
{
return SRT_OUT_OF_CAPACITY;
}
const vec4s* src = rt->buffer;
for (uint32_t y = 0; y < height; ++y)
{
uint8_t* row = dst + (size_t)y * dst_stride_bytes;
for (uint32_t x = 0; x < width; ++x)
{
vec4s pixel = src[(size_t)y * width + x];
pixel = aces_tone_map(pixel);
pixel = gamma_correct(pixel, 2.2f);
float alpha = fminf(fmaxf(pixel.w, 0.0f), 1.0f);
size_t i = (size_t)x * 4;
// BGRA8
row[i + 0] = COLOR_CLAMP(pixel.z * alpha * 255.0f);
row[i + 1] = COLOR_CLAMP(pixel.y * alpha * 255.0f);
row[i + 2] = COLOR_CLAMP(pixel.x * alpha * 255.0f);
row[i + 3] = COLOR_CLAMP(alpha * 255.0f);
}
}
return SRT_OK;
}