1139 lines
30 KiB
C
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;
|
|
}
|