#include "renderer.h"
#include <cmath>
#include <cstdlib>
#include <vector>
#include "passes/cluster/cluster_bounds.h"
#include "passes/cluster/light_cull.h"
#include "passes/ddgi/ddgi_classify.h"
#include "passes/ddgi/ddgi_probes_dbg.h"
#include "passes/ddgi/ddgi_relocate.h"
#include "passes/ddgi/ddgi_trace.h"
#include "passes/ddgi/ddgi_update.h"
#include "passes/debug/debug_lines.h"
#include "passes/depth_pre/depth_normal.h"
#include "passes/dispatch_mdi/build_indirect.h"
#include "passes/fg.h"
#include "passes/fg_utils.h"
#include "passes/forward/forward.h"
#include "passes/fullscreen/fullscreen.h"
#include "passes/grid/grid.h"
#include "passes/hiz/hiz.h"
#include "passes/id_blit/id_blit.h"
#include "passes/rt_build.h"
#include "passes/shadow/shadow.h"
#include "passes/skybox/skybox.h"
#include "passes/ssr/ssr.h"
#include "shaders/renderer_types.h"
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include "asset/asset.h"
#include "asset/texture.h"
#include "core/globals.h"
#include "core/logger.h"
#include "core/math_rtm.h"
#include "core/platform.h"
#include "core/profile.h"
#ifdef ENABLE_EDITOR
#include "editor/editor.h"
#endif
#include "pipelines.h"
#include "quick_submit.h"
#include "scene/camera.h"
#include "scene/ibl.h"
#include "scene/scene.h"
#include "shaders/renderer_types.h"
struct DefaultTexture {
rhi::Image image;
rhi::ImageView view{};
u32 slot = UINT32_MAX;
};
struct DefaultTextures {
DefaultTexture white_srgb;
DefaultTexture white_linear;
DefaultTexture black_srgb;
DefaultTexture flat_normal;
DefaultTexture error;
};
static void build_ubo(Scene &scene, RenderData &rd, rhi::Device &device, FrameData &frame, rhi::Extent2D &extent) {
FrameUBO out{};
Camera camera = scene.cameras[scene.active_camera];
out.view = camera.view;
out.proj = camera.proj;
out.view_proj = camera.view_proj;
out.inv_view = camera.inv_view;
out.inv_proj = camera.inv_proj;
out.inv_view_proj = camera.inv_view_proj;
out.position_ws = vec4(camera.position.x, camera.position.y, camera.position.z, 1.0);
out.forward_ws = camera.forward();
out.near_plane = camera.near_plane;
out.far_plane = camera.far_plane;
out.render_items = rhi::buffer_device_address(device, rd.render_item_buffer);
out.submeshes = rhi::buffer_device_address(device, rd.submesh_buffer);
out.transforms = rhi::buffer_device_address(device, frame.transform_buffer);
out.screen_w = extent.width;
out.screen_h = extent.height;
out.materials = rhi::buffer_device_address(device, rd.material_buffer);
Light light = scene.directional_lights[0];
out.light_dir = light.direction;
out.light_color = light.color;
out.light_intensity = light.intensity;
out.env = scene.env.environmentIdx;
out.env_brdf = scene.env.brdfLutIdx;
out.env_irradiance = scene.env.irradianceIdx;
out.env_prefiltered = scene.env.prefilteredIdx;
out.env_prefilter_mip = scene.env.prefilter_mip_count;
out.main_light_view_proj = light.view_proj;
out.point_lights = rhi::buffer_device_address(device, frame.point_light_buffer);
out.point_light_count = (u32)scene.point_lights.size();
out.tlas_address =
rhi::valid(rd.rt_scene.tlas) ? rhi::get_acceleration_structure_device_address(device, rd.rt_scene.tlas) : 0;
out.debug = rd.debug;
out.aniso_wrap_mips = static_cast<u32>(rd.aniso_wrap_mips);
out.linear_clamp_mips = static_cast<u32>(rd.linear_clamp_mips);
out.linear_clamp = static_cast<u32>(rd.linear_clamp);
out.cmp_greater = static_cast<u32>(rd.cmp_greater);
out.ddgi_grid = rd.ddgi_grid;
out.ddgi_cfg = rd.ddgi_cfg;
memcpy(frame.frame_ubo.mapped, &out, sizeof(out));
}
static void create_gpu_buffer_and_upload(
rhi::Device &device,
Arena &staging,
rhi::Buffer &out_buffer,
const void *data,
usize element_count,
usize element_stride,
QsCmd &cmd,
rhi::BufferUsage usage = rhi::BufferUsage::StorageBuffer,
const char *name = nullptr
) {
const usize total_size = element_count * element_stride;
rhi::BufferDesc info = {};
info.usage = rhi::BufferUsage::CopyDst | rhi::BufferUsage::ShaderAddr | usage;
info.size = total_size;
if (name != nullptr) {
info.name = name;
}
rhi::create_buffer(device, info, out_buffer);
u64 offset = stage_to_arena(staging, data, total_size);
if (offset != UINT64_MAX) {
rhi::buffer_flush(device, staging.buffer, offset, total_size);
rhi::copy_buffer_to_device(device, staging.buffer, offset, out_buffer, 0, total_size, cmd.cmd);
}
}
static void create_frame_data(rhi::Device &device, FrameData *frames, u32 max_transform_count) {
constexpr u32 runtime_transform_headroom = 4096;
u64 ubo_align = device.min_ubo_alignment;
if (ubo_align < 16) {
ubo_align = 16;
}
for (u32 i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
FrameData &frame = frames[i];
{
// FrameUBO
rhi::BufferDesc frame_ubo_desc{};
frame_ubo_desc.size = 64 * 1024; // 2 * 1024 * 1024;
frame_ubo_desc.usage =
rhi::BufferUsage::CopySrc | rhi::BufferUsage::CopyDst | rhi::BufferUsage::UniformBuffer;
frame_ubo_desc.memory = rhi::BufferMemType::Upload;
frame_ubo_desc.name = ("Frame UBO [" + std::to_string(i) + "]").c_str();
rhi::create_buffer(device, frame_ubo_desc, frame.frame_ubo);
}
{
// point lights
rhi::BufferDesc pl_buffer_desc{};
pl_buffer_desc.size = 64 * 1024; // 2 * 1024 * 1024;
pl_buffer_desc.usage = rhi::BufferUsage::CopySrc | rhi::BufferUsage::CopyDst |
rhi::BufferUsage::StorageBuffer | rhi::BufferUsage::ShaderAddr;
pl_buffer_desc.memory = rhi::BufferMemType::Upload;
pl_buffer_desc.name = ("Point Light Buffer [" + std::to_string(i) + "]").c_str();
rhi::create_buffer(device, pl_buffer_desc, frame.point_light_buffer);
}
{
frame.transform_capacity = max_transform_count + runtime_transform_headroom;
rhi::BufferDesc xform_info{};
xform_info.size = frame.transform_capacity * sizeof(TransformGPU);
xform_info.usage = rhi::BufferUsage::StorageBuffer | rhi::BufferUsage::ShaderAddr;
xform_info.memory = rhi::BufferMemType::Upload;
xform_info.name = "Transform Buffer [" + std::to_string(i) + "]";
rhi::create_buffer(device, xform_info, frame.transform_buffer);
}
}
}
static void upload_transforms(Scene &scene, FrameData &frame) {
TransformGPU *dst = (TransformGPU *)frame.transform_buffer.mapped;
const u32 capacity = frame.transform_capacity;
SceneGraph &sg = scene.scene_graph;
for (u32 n : scene.mesh_nodes) {
const u32 base = sg.instance_base[n];
const u32 ic = sg.instance_count[n];
if (ic > 0) {
for (u32 k = 0; k < ic; ++k) {
u32 slot = base + k;
if (slot >= capacity) {
break;
}
// instance TRS under the node's own locals under the parent world.
mat4 instance_world = scene.instance_world(n, k);
dst[slot].active = 1;
dst[slot].world = instance_world;
dst[slot].normal_world = inverse(instance_world);
}
} else {
if (base >= capacity) {
continue;
}
dst[base].active = sg.is_node_alive(n) ? 1 : 0;
dst[base].world = sg.worlds[n];
dst[base].normal_world = inverse(sg.worlds[n]);
}
}
}
static void upload_scene_data(Scene &scene, FrameData &frame) {
upload_transforms(scene, frame);
u32 pl_count = (u32)std::min(scene.point_lights.size(), (usize)MAX_POINT_LIGHTS);
PointLightGPU *pl_dst = (PointLightGPU *)frame.point_light_buffer.mapped;
for (u32 i = 0; i < pl_count; ++i) {
pl_dst[i].position = scene.point_lights[i].position;
pl_dst[i].range = scene.point_lights[i].range;
pl_dst[i].color = scene.point_lights[i].color;
pl_dst[i].intensity = scene.point_lights[i].intensity;
}
}
static void create_render_data(
rhi::Device &device,
QuickSubmit &qs,
AssetManager &assets,
const Scene &scene,
RenderData &out,
DefaultTextures &default_textures,
FrameGraph &fg
) {
const u32 asset_count = assets.count();
std::vector<u32> submesh_offset(asset_count, 0);
std::vector<u32> material_offset(asset_count, 0);
u32 total_submeshes = 0;
u32 total_materials = 0;
for (u32 ai = 0; ai < asset_count; ++ai) {
const AssetScene *in = assets.get({ai});
submesh_offset[ai] = total_submeshes;
material_offset[ai] = total_materials;
total_submeshes += (u32)in->submeshes.size();
total_materials += (u32)in->materials.size();
}
out.submeshes.resize(total_submeshes);
for (u32 ai = 0; ai < asset_count; ++ai) {
const AssetScene &in = *assets.get({ai});
for (usize i = 0; i < in.submeshes.size(); ++i) {
const AssetSubmesh &src = in.submeshes[i];
const AssetGeometry &geom = in.geometries[src.geometry];
SubmeshGPU &dst = out.submeshes[submesh_offset[ai] + i];
dst.first_index = (u32)out.indices.size();
dst.index_count = geom.index_count;
dst.base_vertex = (u32)out.vertices.size();
dst.local_aabb_min = src.aabb_min;
dst.local_aabb_max = src.aabb_max;
dst.local_sphere = src.sphere;
const Vertex *vsrc = in.vertices.data() + geom.first_vertex;
out.vertices.insert(out.vertices.end(), vsrc, vsrc + geom.vertex_count);
const u32 *isrc = in.indices.data() + geom.first_index;
out.indices.insert(out.indices.end(), isrc, isrc + geom.index_count);
}
}
auto load = [&](const AssetScene &in, u32 tex_idx, ImageColorSpace cs, u64 fallback) -> u64 {
if (tex_idx == ASSET_INVALID) {
return fallback;
}
assert(tex_idx < in.textures.size());
const AssetTexture &tex = in.textures[tex_idx];
rhi::Image img;
rhi::ImageView view{};
bool ok = false;
std::string tex_name = "tex/" + (!tex.path.empty() ? tex.path : ("blob#" + std::to_string(tex_idx)));
if (tex.blob && tex.blob_size > 0) {
ok = tex.is_ktx ? load_texture_ktx2_from_memory(
device, qs, out.staging, img, view, tex.blob, tex.blob_size, cs, tex_name.c_str()
)
: load_texture_from_memory(
device, qs, out.staging, img, view, tex.blob, tex.blob_size, cs, tex_name.c_str()
);
} else if (!tex.path.empty()) {
ok = load_texture(device, qs, out.staging, tex.path.c_str(), img, view, cs);
}
if (!ok) {
return rhi::handle_id(device, default_textures.error.view);
}
const u64 handle = rhi::handle_id(device, view);
out.loaded_textures.emplace_back(std::move(img), std::move(view));
auto &stored = out.loaded_textures.back();
stored.second.desc.image = &stored.first;
return handle;
};
out.materials.reserve(std::max<usize>(total_materials, 1));
for (u32 ai = 0; ai < asset_count; ++ai) {
const AssetScene &in = *assets.get({ai});
for (const AssetMaterial &mat : in.materials) {
MaterialGPU gpu{};
gpu.base_color = mat.base_color;
gpu.metallic_factor = mat.metallic;
gpu.roughness_factor = mat.roughness;
gpu.emissive_factor = mat.emissive;
gpu.albedo = load(
in, mat.base_color_tex, ImageColorSpace::SRGB, rhi::handle_id(device, default_textures.white_srgb.view)
);
gpu.normal = load(
in, mat.normal_tex, ImageColorSpace::Linear, rhi::handle_id(device, default_textures.flat_normal.view)
);
gpu.metal_rough = load(
in, mat.mr_tex, ImageColorSpace::Linear, rhi::handle_id(device, default_textures.white_linear.view)
);
gpu.emissive = load(
in, mat.emissive_tex, ImageColorSpace::SRGB, rhi::handle_id(device, default_textures.black_srgb.view)
);
out.materials.push_back(gpu);
}
}
if (out.materials.empty()) {
MaterialGPU mat{};
mat.base_color = vec4(0.5f, 0.5f, 0.5f, 1.0f);
mat.metallic_factor = 0.5f;
mat.roughness_factor = 0.5f;
mat.emissive_factor = vec3(0.0f, 0.0f, 0.0f);
mat.albedo = rhi::handle_id(device, default_textures.white_srgb.view);
mat.normal = rhi::handle_id(device, default_textures.flat_normal.view);
mat.metal_rough = rhi::handle_id(device, default_textures.white_linear.view);
mat.emissive = rhi::handle_id(device, default_textures.black_srgb.view);
out.materials.push_back(mat);
}
const SceneGraph &sg = scene.scene_graph;
for (u32 n : scene.mesh_nodes) {
AssetHandle ah = sg.asset[n];
if (!ah.valid()) {
continue;
}
const AssetScene *asset = assets.get(ah);
if (!asset) {
continue;
}
u32 ai = sg.asset_index[n];
assert(ai < asset->nodes.size());
const AssetNode &node = asset->nodes[ai];
if (node.mesh == ASSET_INVALID) {
continue;
}
assert(node.mesh < asset->meshes.size());
const AssetMesh &mesh = asset->meshes[node.mesh];
u32 inst_count = sg.instance_count[n];
if (inst_count == 0) {
inst_count = 1;
}
u32 base = sg.instance_base[n];
for (u32 si = 0; si < mesh.submesh_count; ++si) {
u32 sm_idx = asset->mesh_submeshes[mesh.first_submesh + si];
assert(sm_idx < asset->submeshes.size());
u32 mat = asset->submeshes[sm_idx].material;
u32 material_index = material_offset[ah.id] + ((mat == ASSET_INVALID) ? 0 : mat);
u32 submesh_index = submesh_offset[ah.id] + sm_idx;
for (u32 k = 0; k < inst_count; ++k) {
RenderItemGPU item{};
item.instance_id = base + k;
item.submesh_index = submesh_index;
item.material_index = material_index;
// +1 so the ID-blit clear value (0) means "no hit"; editor subtracts 1.
// I might just get rid of this, and raycast instead
item.entity_id = n + 1;
out.render_items.push_back(item);
}
}
}
out.render_item_count = (u32)out.render_items.size();
}
static bool create_rt_scene(rhi::Device &device, rhi::CmdBuffer *cmd, RenderData &rd) {
RtScene &rt = rd.rt_scene;
u32 total_verts = (u32)rd.vertices.size();
u32 total_indices = (u32)rd.indices.size();
if (total_verts == 0 || total_indices == 0 || rd.submeshes.empty()) {
VEL_WARN("create_rt_scene: skipped (no vertex/index/submesh data)");
return false;
}
u64 vb_addr = rhi::buffer_device_address(device, rd.vertex_buffer);
u64 ib_addr = rhi::buffer_device_address(device, rd.index_buffer);
u32 max_instances = rd.render_item_count;
rd.mesh_rt.clear();
rd.mesh_rt.resize(rd.submeshes.size());
std::vector<rhi::AccelStructBuildSizesInfo> blas_sizes(rd.submeshes.size());
std::vector<u32> primitive_counts(rd.submeshes.size());
u64 max_blas_scratch_size = 0;
auto blas_build_flags = [](bool dynamic) -> rhi::AccelStructBuildFlags {
return dynamic ? rhi::AccelStructBuildFlags::AllowUpdate : rhi::AccelStructBuildFlags::PreferFastTrace;
};
for (u32 mesh_id = 0; mesh_id < (u32)rd.submeshes.size(); ++mesh_id) {
const SubmeshGPU &submesh = rd.submeshes[mesh_id];
primitive_counts[mesh_id] = submesh.index_count / 3;
if (primitive_counts[mesh_id] == 0) {
continue;
}
MeshRT &mesh_rt = rd.mesh_rt[mesh_id];
rhi::AccelStructGeometryTrianglesData triangles{
.vertex_format = rhi::VertexFormat::R32G32B32_FLOAT,
.vertex_data = vb_addr + offsetof(Vertex, pos),
.vertex_stride = sizeof(Vertex),
.max_vertex = total_verts - 1,
.index_type = rhi::IndexType::UINT32,
.index_data = ib_addr + (u64)submesh.first_index * sizeof(u32),
};
rhi::AccelStructGeometry as_geom{
.type = rhi::AccelStructGeometryType::Triangles,
.flags = rhi::AccelStructGeometryFlags::Opaque,
.triangles = triangles,
};
rhi::AccelStructBuildGeometryInfo build_info{
.type = rhi::AccelStructType::BottomLevel,
.flags = blas_build_flags(mesh_rt.dynamic),
.geometries = &as_geom,
.geometry_count = 1,
};
rhi::get_acceleration_structure_build_sizes(
device, build_info, &primitive_counts[mesh_id], blas_sizes[mesh_id]
);
max_blas_scratch_size = std::max(max_blas_scratch_size, blas_sizes[mesh_id].build_scratch_size);
}
if (max_blas_scratch_size == 0) {
VEL_WARN("create_rt_scene: skipped (no BLAS geometry)");
return false;
}
u64 tlas_build_scratch_size = 0;
if (max_instances > 0) {
rhi::AccelStructGeometry tlas_size_geom{
.type = rhi::AccelStructGeometryType::Instances,
.flags = rhi::AccelStructGeometryFlags::Opaque,
};
rhi::AccelStructBuildGeometryInfo tlas_size_info{
.type = rhi::AccelStructType::TopLevel,
.flags = rhi::AccelStructBuildFlags::PreferFastTrace,
.geometries = &tlas_size_geom,
.geometry_count = 1,
};
rhi::AccelStructBuildSizesInfo tlas_size_query{};
rhi::get_acceleration_structure_build_sizes(device, tlas_size_info, &max_instances, tlas_size_query);
tlas_build_scratch_size = tlas_size_query.build_scratch_size;
}
const u64 scratch_size = std::max(max_blas_scratch_size, tlas_build_scratch_size);
rhi::BufferDesc scratch_desc{};
scratch_desc.size = scratch_size;
scratch_desc.usage = rhi::BufferUsage::StorageBuffer | rhi::BufferUsage::ShaderAddr;
scratch_desc.memory = rhi::BufferMemType::Device;
scratch_desc.dedicated = true;
scratch_desc.name = "rt/accel-scratch";
rhi::create_buffer(device, scratch_desc, rt.scratch_buffer);
rt.scratch_size = scratch_size;
rt.scratch_device_addr = rhi::buffer_device_address(device, rt.scratch_buffer);
rhi::barrier(*cmd, rd.vertex_buffer, rhi::ResourceState::TransferTo, rhi::ResourceState::AccelBuild);
rhi::barrier(*cmd, rd.index_buffer, rhi::ResourceState::TransferTo, rhi::ResourceState::AccelBuild);
// BLAS: one acceleration structure per submesh
for (u32 mesh_id = 0; mesh_id < (u32)rd.submeshes.size(); ++mesh_id) {
if (primitive_counts[mesh_id] == 0) {
continue;
}
const SubmeshGPU &submesh = rd.submeshes[mesh_id];
MeshRT &mesh_rt = rd.mesh_rt[mesh_id];
rhi::BufferDesc blas_desc{};
blas_desc.size = blas_sizes[mesh_id].acceleration_structure_size;
blas_desc.usage = rhi::BufferUsage::AccelStructStorage | rhi::BufferUsage::ShaderAddr;
blas_desc.memory = rhi::BufferMemType::Device;
blas_desc.dedicated = true;
blas_desc.name = "rt/blas[submesh#" + std::to_string(mesh_id) + "]";
rhi::create_buffer(device, blas_desc, mesh_rt.blas_buffer);
rhi::create_acceleration_structure(
device,
{.buffer = &mesh_rt.blas_buffer,
.size = blas_sizes[mesh_id].acceleration_structure_size,
.type = rhi::AccelStructType::BottomLevel,
.name = "rt/blas[submesh#" + std::to_string(mesh_id) + "]"},
mesh_rt.blas
);
rhi::AccelStructGeometryTrianglesData triangles{
.vertex_format = rhi::VertexFormat::R32G32B32_FLOAT,
.vertex_data = vb_addr + offsetof(Vertex, pos),
.vertex_stride = sizeof(Vertex),
.max_vertex = total_verts - 1,
.index_type = rhi::IndexType::UINT32,
.index_data = ib_addr + (u64)submesh.first_index * sizeof(u32),
};
rhi::AccelStructGeometry as_geom{
.type = rhi::AccelStructGeometryType::Triangles,
.flags = rhi::AccelStructGeometryFlags::Opaque,
.triangles = triangles,
};
rhi::AccelStructBuildGeometryInfo build_info{
.type = rhi::AccelStructType::BottomLevel,
.flags = blas_build_flags(mesh_rt.dynamic),
.dst_acceleration_structure = &mesh_rt.blas,
.geometries = &as_geom,
.geometry_count = 1,
.scratch_data = rt.scratch_device_addr,
};
rhi::AccelStructBuildRangeInfo range{
.primitive_count = primitive_counts[mesh_id],
.primitive_offset = 0,
.first_vertex = submesh.base_vertex,
.transform_offset = 0,
};
const rhi::AccelStructBuildRangeInfo *p_range = ⦥
rhi::cmd_build_acceleration_structures(*cmd, &build_info, &p_range, 1);
mesh_rt.blas_device_address = rhi::get_acceleration_structure_device_address(device, mesh_rt.blas);
if (mesh_id + 1 < (u32)rd.submeshes.size()) {
rhi::barrier(*cmd, rhi::ResourceState::AccelBuild, rhi::ResourceState::AccelBuild);
}
}
rhi::barrier(*cmd, rhi::ResourceState::AccelBuild, rhi::ResourceState::AccelBuild);
// Instance buffer for per-frame TLAS build (CPU-writable)
u64 inst_buf_size = std::max(1u, max_instances) * sizeof(rhi::AccelStructInstance);
rhi::BufferDesc inst_desc{};
inst_desc.size = inst_buf_size;
inst_desc.usage = rhi::BufferUsage::AccelStructBuildInput | rhi::BufferUsage::ShaderAddr;
inst_desc.memory = rhi::BufferMemType::Upload;
inst_desc.name = "rt/tlas-instances";
rhi::create_buffer(device, inst_desc, rt.instance_buffer);
rt.instance_buffer_size = inst_buf_size;
// Instance metadata buffer (GPU-readable)
u64 inst_data_size = std::max(1u, max_instances) * sizeof(InstanceData);
rhi::BufferDesc inst_data_desc{};
inst_data_desc.size = inst_data_size;
inst_data_desc.usage = rhi::BufferUsage::StorageBuffer | rhi::BufferUsage::ShaderAddr;
inst_data_desc.memory = rhi::BufferMemType::Upload;
inst_data_desc.name = "rt/instance-data";
rhi::create_buffer(device, inst_data_desc, rt.instance_data_buffer);
// TLAS backing buffer and acceleration structure (pre-allocated, reused every frame)
if (max_instances > 0) {
u64 inst_dev_addr = rhi::buffer_device_address(device, rt.instance_buffer);
rhi::AccelStructGeometryInstancesData instances_data{
.data = inst_dev_addr,
.array_of_pointers = false,
};
rhi::AccelStructGeometry tlas_geom{
.type = rhi::AccelStructGeometryType::Instances,
.flags = rhi::AccelStructGeometryFlags::Opaque,
.instances = instances_data,
};
rhi::AccelStructBuildGeometryInfo tlas_info{
.type = rhi::AccelStructType::TopLevel,
.flags = rhi::AccelStructBuildFlags::PreferFastTrace,
.geometries = &tlas_geom,
.geometry_count = 1,
};
rhi::AccelStructBuildSizesInfo tlas_sizes{};
rhi::get_acceleration_structure_build_sizes(device, tlas_info, &max_instances, tlas_sizes);
rhi::BufferDesc tlas_desc{};
tlas_desc.size = tlas_sizes.acceleration_structure_size;
tlas_desc.usage = rhi::BufferUsage::AccelStructStorage | rhi::BufferUsage::ShaderAddr;
tlas_desc.memory = rhi::BufferMemType::Device;
tlas_desc.dedicated = true;
tlas_desc.name = "rt/tlas";
rhi::create_buffer(device, tlas_desc, rt.tlas_buffer);
rt.tlas_buffer_size = tlas_sizes.acceleration_structure_size;
rhi::create_acceleration_structure(
device,
{.buffer = &rt.tlas_buffer,
.size = tlas_sizes.acceleration_structure_size,
.type = rhi::AccelStructType::TopLevel,
.name = "rt/tlas"},
rt.tlas
);
rhi::barrier(*cmd, rt.tlas_buffer, rhi::ResourceState::Idle, rhi::ResourceState::AccelBuild);
{
rhi::AccelStructGeometryInstancesData init_instances{
.data = inst_dev_addr,
.array_of_pointers = false,
};
rhi::AccelStructGeometry init_geom{
.type = rhi::AccelStructGeometryType::Instances,
.flags = rhi::AccelStructGeometryFlags::Opaque,
.instances = init_instances,
};
rhi::AccelStructBuildGeometryInfo init_build{
.type = rhi::AccelStructType::TopLevel,
.flags = rhi::AccelStructBuildFlags::PreferFastTrace,
.dst_acceleration_structure = &rt.tlas,
.geometries = &init_geom,
.geometry_count = 1,
.scratch_data = rt.scratch_device_addr,
};
rhi::AccelStructBuildRangeInfo init_range{
.primitive_count = 0,
.primitive_offset = 0,
.first_vertex = 0,
.transform_offset = 0,
};
const rhi::AccelStructBuildRangeInfo *p_init_range = &init_range;
rhi::cmd_build_acceleration_structures(*cmd, &init_build, &p_init_range, 1);
}
rhi::barrier(*cmd, rhi::ResourceState::AccelBuild, rhi::ResourceState::AccelBuild);
}
// TLAS now BDA via FrameUBO.tlas_address — no descriptor arena
return true;
};
static void destroy_rt_scene(rhi::Device &device, RenderData &rd) {
RtScene &rt = rd.rt_scene;
rhi::destroy_acceleration_structure(device, rt.tlas);
for (MeshRT &mesh_rt : rd.mesh_rt) {
rhi::destroy_acceleration_structure(device, mesh_rt.blas);
if (rhi::valid(mesh_rt.blas_buffer)) {
rhi::destroy_buffer(device, mesh_rt.blas_buffer);
mesh_rt.blas_buffer = {};
}
}
rd.mesh_rt.clear();
if (rhi::valid(rt.scratch_buffer)) {
rhi::destroy_buffer(device, rt.scratch_buffer);
rt.scratch_buffer = {};
}
if (rhi::valid(rt.instance_buffer)) {
rhi::destroy_buffer(device, rt.instance_buffer);
rt.instance_buffer = {};
}
if (rhi::valid(rt.instance_data_buffer)) {
rhi::destroy_buffer(device, rt.instance_data_buffer);
rt.instance_data_buffer = {};
}
if (rhi::valid(rt.tlas_buffer)) {
rhi::destroy_buffer(device, rt.tlas_buffer);
rt.tlas_buffer = {};
}
rt = {};
}
static void destroy_default_textures(rhi::Device &device, DefaultTextures &tex) {
auto destroy = [&](DefaultTexture &t) {
rhi::destroy_image_view(device, t.view);
rhi::destroy_image(device, t.image);
};
destroy(tex.white_srgb);
destroy(tex.white_linear);
destroy(tex.black_srgb);
destroy(tex.flat_normal);
destroy(tex.error);
}
static f32 randf(f32 min, f32 max) {
return min + (max - min) * (f32(rand()) / f32(RAND_MAX));
}
static u32 scatter_instances(AssetManager &assets, Scene &scene, AssetHandle ah, u32 count, f32 spread) {
const AssetScene *asset = assets.get(ah);
if (!asset) {
return SCENE_INVALID;
}
u32 base = scene.scene_graph.node_count;
u32 root = scene.instantiate(assets, ah);
if (root == SCENE_INVALID) {
return SCENE_INVALID;
}
// asset_node_index that has the first mesh
u32 mesh_asset_node = SCENE_INVALID;
for (u32 i = 0; i < (u32)asset->nodes.size(); ++i) {
if (asset->nodes[i].mesh != ASSET_INVALID) {
mesh_asset_node = i;
break;
}
}
if (mesh_asset_node == SCENE_INVALID) {
return SCENE_INVALID;
}
u32 mesh_node = base + mesh_asset_node;
// instance locals
u32 offset = (u32)scene.instance_locals.size();
scene.instance_locals.reserve(offset + count);
for (u32 i = 0; i < count; ++i) {
TRS trs;
trs.translation = vec3(randf(-spread, spread), randf(-spread, spread), randf(-spread, spread));
trs.rotation =
quat::from_axis_angle(vec3(randf(-1, 1), randf(-1, 1), randf(-1, 1)).normalized(), randf(0, 6.283f));
scene.instance_locals.push_back(trs);
}
SceneGraph &sg = scene.scene_graph;
sg.instance_count[mesh_node] = count;
sg.instance_base[mesh_node] = offset;
sg.update_world_transforms();
return root;
}
static void scatter_point_lights(Scene &scene, u32 count) {
scene.point_lights.reserve(count);
for (u32 i = 0; i < count; ++i) {
vec3 pos = {randf(-8.0f, 8.0f), randf(-12.0f, 12.0f), randf(0.2f, 9.5f)};
vec3 col = {randf(0.0f, 1.0f), randf(0.0f, 1.0f), randf(0.0f, 1.0f)};
NodeHandle pl_node = scene.scene_graph.add_node(-1, TRS{.translation = pos});
PointLight pl;
pl.node = pl_node;
pl.color = col;
pl.intensity = 5.0f;
pl.range = 2.0f;
scene.point_lights.push_back(pl);
}
}
int main() {
vel::Logger::init();
rhi::Config config{};
rhi::ShaderCompiler sc;
rhi::Device device{};
rhi::Swapchain swapchain{};
rhi::Sync *frame_sync = nullptr;
rhi::CmdPool frame_pools[MAX_FRAMES_IN_FLIGHT]{};
rhi::CmdBuffer frame_cmds[MAX_FRAMES_IN_FLIGHT]{};
FrameGraph fg;
config.vsync_enabled = true;
config.enable_validation = true;
Platform platform{};
QuickSubmit quick_submit;
init_compiler(sc);
init_platform(config.instance_extensions);
create_context(device, config);
create_memory_allocator(device);
qs_init(quick_submit, rhi::graphics_queue(device), device);
platform.width = 1920;
platform.height = 1080;
create_window(device, platform, "gfx-playground", WindowMode::Windowed, true);
rhi::SwapchainDesc swapchain_desc{};
swapchain_desc.native_window = platform_native_window(platform);
swapchain_desc.width = platform.width;
swapchain_desc.height = platform.height;
swapchain_desc.vsync_enabled = config.vsync_enabled;
swapchain_desc.image_count = config.swapchain_image_count;
create_swapchain(device, swapchain_desc, swapchain);
frame_sync = rhi::sync_create(device, 0, "frame");
if (!rhi::valid(frame_sync)) {
VEL_CRITICAL("Failed to create frame pacing timeline");
return 1;
}
for (u32 i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
rhi::create_cmd_pool(
device,
rhi::CmdPoolDesc{
.queue_family_index = rhi::queue_family_index(rhi::graphics_queue(device)),
.flags = rhi::CmdPoolUsage::Transient | rhi::CmdPoolUsage::ResetCommandBuffer,
},
frame_pools[i]
);
rhi::CmdBufferDesc cmd_desc;
cmd_desc.pool = &frame_pools[i];
cmd_desc.level = rhi::CmdBufferLevel::Primary;
rhi::create_cmd_buffer(device, cmd_desc, frame_cmds[i]);
}
#ifdef ENABLE_EDITOR
editor::Editor editor;
#endif
Pipelines pipelines(device, sc);
AssetManager assets;
RenderData render_data{};
FrameData frame_data[MAX_FRAMES_IN_FLIGHT]{};
DefaultTextures textures;
Scene scene;
rhi::Extent2D render_size;
{
NodeHandle cam_node = scene.scene_graph.add_node(
-1,
TRS{
.translation = vec3(-3.6f, -1.2f, 3.7f),
}
);
Camera cam;
cam.node = cam_node;
scene.cameras.push_back(cam);
scene.active_camera = (u32)scene.cameras.size() - 1;
NodeHandle cam2_node = scene.scene_graph.add_node(
-1,
TRS{
.translation = vec3(5.0f, 5.0f, 5.0f),
}
);
Camera cam2;
cam2.node = cam2_node;
scene.cameras.push_back(cam2);
}
{
vec3 target_dir = vec3(0.2f, 0.2f, 1.0f).normalized();
vec3 fwd = vec3(1.0f, 0.0f, 0.0f);
vec3 axis = cross(fwd, target_dir).normalized();
f32 angle = std::acos(std::clamp(fwd.dot(target_dir), -1.0f, 1.0f));
NodeHandle light_node = scene.scene_graph.add_node(
-1,
TRS{
.rotation = quat::from_axis_angle(axis, angle),
}
);
Light dl;
dl.node = light_node;
scene.directional_lights.push_back(dl);
}
fg.device = &device;
rhi::BufferDesc staging_arena_buffer_desc{};
staging_arena_buffer_desc.size = 8ull * 1024 * 1024 * 1024;
staging_arena_buffer_desc.usage = rhi::BufferUsage::CopySrc;
staging_arena_buffer_desc.memory = rhi::BufferMemType::Upload;
staging_arena_buffer_desc.name = "Staging Arena";
rhi::BufferViewDesc staging_arena_view_desc{};
staging_arena_view_desc.type = rhi::BufferViewType::Storage;
create_arena(device, render_data.staging, staging_arena_buffer_desc, staging_arena_view_desc);
{
rhi::Sampler shadow_sampler;
rhi::SamplerDesc compare_sampler_info;
compare_sampler_info.filter = rhi::SamplerFilter::LINEAR;
compare_sampler_info.address = rhi::SamplerAddressMode::CLAMP;
compare_sampler_info.comparison_func = rhi::SamplerComparisonFunc::GREATER_EQUAL;
compare_sampler_info.use_mips = false;
compare_sampler_info.name = "samp/shadow-compare";
rhi::create_sampler(device, compare_sampler_info, shadow_sampler);
render_data.cmp_greater = rhi::handle_id(device, shadow_sampler);
rhi::Sampler material_sampler;
rhi::SamplerDesc linear_sampler_info;
linear_sampler_info.filter = rhi::SamplerFilter::ANISOTROPIC;
linear_sampler_info.address = rhi::SamplerAddressMode::WRAP;
linear_sampler_info.comparison_func = rhi::SamplerComparisonFunc::NEVER;
linear_sampler_info.use_mips = true;
linear_sampler_info.name = "samp/material-aniso-wrap";
rhi::create_sampler(device, linear_sampler_info, material_sampler);
render_data.aniso_wrap_mips = rhi::handle_id(device, material_sampler);
rhi::Sampler atlas_sampler;
rhi::SamplerDesc atlas_sampler_info;
atlas_sampler_info.filter = rhi::SamplerFilter::LINEAR;
atlas_sampler_info.address = rhi::SamplerAddressMode::CLAMP;
atlas_sampler_info.comparison_func = rhi::SamplerComparisonFunc::NEVER;
atlas_sampler_info.use_mips = false;
atlas_sampler_info.name = "samp/atlas-linear-clamp";
rhi::create_sampler(device, atlas_sampler_info, atlas_sampler);
render_data.linear_clamp = rhi::handle_id(device, atlas_sampler);
rhi::Sampler linear_mip_sampler;
rhi::SamplerDesc linear_mip_info;
linear_mip_info.filter = rhi::SamplerFilter::LINEAR;
linear_mip_info.address = rhi::SamplerAddressMode::CLAMP;
linear_mip_info.comparison_func = rhi::SamplerComparisonFunc::NEVER;
linear_mip_info.use_mips = true;
linear_mip_info.name = "samp/linear-clamp-mips";
rhi::create_sampler(device, linear_mip_info, linear_mip_sampler);
render_data.linear_clamp_mips = rhi::handle_id(device, linear_mip_sampler);
}
{
auto push_tex = [&](DefaultTexture &dst,
const void *pixels,
u32 pixel_count,
u32 pixel_stride,
rhi::ImageFormat cs,
const char *name) {
if (!create_texture_from_raw_rgba(
device,
quick_submit,
render_data.staging,
dst.image,
dst.view,
pixels,
pixel_count,
pixel_count,
pixel_stride,
cs,
name
)) {
VEL_CRITICAL("Failed to create default texture");
return;
}
dst.slot = (u32)dst.view.slot;
};
u8 white[] = {192, 192, 192, 255};
u8 default_metal_rough[] = {0, 128, 128, 255};
u8 black[] = {0, 0, 0, 255};
u8 error[] = {255, 0, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 0, 255, 255};
u8 flat_normal[] = {128, 128, 255, 255};
push_tex(textures.white_srgb, white, 1, 4, rhi::ImageFormat::RGBA8_SRGB, "tex/default/white-srgb");
push_tex(
textures.white_linear, default_metal_rough, 1, 4, rhi::ImageFormat::RGBA8_UNORM, "tex/default/white-linear"
);
push_tex(textures.black_srgb, black, 1, 4, rhi::ImageFormat::RGBA8_SRGB, "tex/default/black");
push_tex(textures.flat_normal, flat_normal, 1, 4, rhi::ImageFormat::RGBA8_UNORM, "tex/default/flat-normal");
push_tex(textures.error, error, 4, 4, rhi::ImageFormat::RGBA8_SRGB, "tex/default/error");
}
#ifdef ENABLE_EDITOR
editor::init(editor, device, platform, quick_submit, render_data.staging, fg, assets);
editor::eval_render_size(editor.viewport_width, editor.viewport_height, swapchain, render_size);
#else
render_size = rhi::swapchain_extent(swapchain);
#endif
qs_wait_all(quick_submit);
reset_arena(render_data.staging);
if (!load_texture(
device,
quick_submit,
render_data.staging,
"assets/kloofendal_overcast_puresky_4k.hdr",
render_data.sky,
render_data.sky_view,
ImageColorSpace::Linear
)) {
VEL_ERROR("Failed to load HDR IMAGE, using fallback");
}
GenerateIBL(device, quick_submit, sc, scene.env, fg, render_data.sky_view, render_data.linear_clamp_mips);
AssetHandle sponza = assets.load("assets/gltf/Sponza-KTX.glb");
AssetHandle helmet = assets.load("assets/gltf/DamagedHelmet.glb");
u32 sponza_root = scene.instantiate(assets, sponza);
u32 helmet_root = scene.instantiate(assets, helmet);
// scatter_instances(assets, scene, helmet, 5000, 100.0f);
// scatter_point_lights(scene, 512);
// scene.scene_graph.set_scale(helmet_root, vec3(50.0, 50.0, 50.0f));
// scene.scene_graph.set_translation(helmet_root, vec3(0.0, 0.0, -1200.0f));
// scene.scene_graph.update_world_transforms();
create_render_data(device, quick_submit, assets, scene, render_data, textures, fg);
create_frame_data(device, frame_data, (u32)scene.instance_locals.size());
VEL_INFO("node_count={}, render_items={}", scene.scene_graph.node_count, render_data.render_items.size());
render_data.scene_graph = &scene.scene_graph;
upload_transforms(scene, frame_data[0]);
QsCmd *cmd = qs_acquire(quick_submit);
create_gpu_buffer_and_upload(
device,
render_data.staging,
render_data.vertex_buffer,
render_data.vertices.data(),
render_data.vertices.size(),
sizeof(Vertex),
*cmd,
rhi::BufferUsage::VertexBuffer | rhi::BufferUsage::StorageBuffer | rhi::BufferUsage::ShaderAddr |
rhi::BufferUsage::AccelStructBuildInput,
"buf/scene-vertices"
);
create_gpu_buffer_and_upload(
device,
render_data.staging,
render_data.index_buffer,
render_data.indices.data(),
render_data.indices.size(),
sizeof(u32),
*cmd,
rhi::BufferUsage::IndexBuffer | rhi::BufferUsage::AccelStructBuildInput,
"buf/scene-indices"
);
create_gpu_buffer_and_upload(
device,
render_data.staging,
render_data.submesh_buffer,
render_data.submeshes.data(),
render_data.submeshes.size(),
sizeof(SubmeshGPU),
*cmd,
rhi::BufferUsage::StorageBuffer,
"buf/scene-submeshes"
);
create_gpu_buffer_and_upload(
device,
render_data.staging,
render_data.material_buffer,
render_data.materials.data(),
render_data.materials.size(),
sizeof(MaterialGPU),
*cmd,
rhi::BufferUsage::StorageBuffer,
"buf/scene-materials"
);
create_gpu_buffer_and_upload(
device,
render_data.staging,
render_data.render_item_buffer,
render_data.render_items.data(),
render_data.render_items.size(),
sizeof(RenderItemGPU),
*cmd,
rhi::BufferUsage::StorageBuffer,
"buf/scene-render-items"
);
qs_submit(quick_submit, *cmd);
reset_arena(render_data.staging);
render_data.vertex_bda = rhi::buffer_device_address(device, render_data.vertex_buffer);
render_data.index_bda = rhi::buffer_device_address(device, render_data.index_buffer);
{
if (!rhi::create_buffer_view(
device,
{.buffer = &render_data.vertex_buffer, .type = rhi::BufferViewType::Storage},
render_data.vertex_view
)) {
VEL_CRITICAL("Failed to create vertex buffer view");
}
for (u32 i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
if (!rhi::create_buffer_view(
device,
{.buffer = &frame_data[i].frame_ubo, .type = rhi::BufferViewType::Uniform},
render_data.frame_ubo_view[i]
)) {
VEL_CRITICAL("Failed to create frame UBO view");
}
}
}
u64 frame_count = 0;
PassTimings pass_timings;
u32 cascade_sizes[Light::CASCADE_COUNT] = {};
resolve_cascade_sizes(render_data.cascade_size, cascade_sizes);
u32 render_item_count = (u32)render_data.render_items.size();
BufferHandle render_items_buf = fg.import_buffer(&render_data.render_item_buffer);
BufferHandle submesh_buf = fg.import_buffer(&render_data.submesh_buffer);
ImageHandle env_cubemap = fg.import_image(&scene.env.environment_img, rhi::ResourceState::TextureSample);
// should handle this better instead of forcing some initial state?
scene.env.environment_img.state = rhi::ResourceState::TextureSample;
auto hiz_res = hiz::create(fg);
auto ddgi_res = ddgi_trace::create(fg);
render_data.ddgi_grid = ddgi_res.grid;
render_data.ddgi_cfg = ddgi_res.cfg;
auto main_bd = build_indirect::create(fg, render_item_count, "main");
auto shadow_bd = build_indirect::create(fg, render_item_count, "shadow");
build_indirect::record(
device, pipelines, sc, fg, render_items_buf, submesh_buf, main_bd, hiz_res.hiz, true, "Main Build Indirect"
);
build_indirect::record(
device,
pipelines,
sc,
fg,
render_items_buf,
submesh_buf,
shadow_bd,
ImageHandle{},
false,
"Shadow Build Indirect"
);
auto shadow_map = shadow::record(
device, pipelines, sc, fg, shadow_bd.indirect, shadow_bd.draw_count, shadow_bd.visible, cascade_sizes
);
auto dn = depth_normal::record(device, pipelines, sc, fg, main_bd.indirect, main_bd.draw_count, main_bd.visible);
auto clusters = cluster_bounds::record(device, pipelines, sc, fg, swapchain.extent.width, swapchain.extent.height);
auto light_cull = light_cull::record(device, pipelines, sc, fg, clusters.cluster_bounds, clusters.cluster_records);
auto forward = forward::record(
device,
pipelines,
sc,
fg,
main_bd.indirect,
main_bd.draw_count,
main_bd.visible,
shadow_map.cascade,
clusters.cluster_bounds,
clusters.cluster_records,
light_cull.light_index_list,
ddgi_res,
dn.depth
);
{ // ddgi (deegee)
QsCmd *cmd = qs_acquire(quick_submit);
create_rt_scene(device, &cmd->cmd, render_data);
qs_submit(quick_submit, *cmd);
qs_wait_all(quick_submit);
BufferHandle tlas_buf = fg.import_buffer(&render_data.rt_scene.tlas_buffer, rhi::ResourceState::AccelBuild);
rt_build::record(fg, tlas_buf);
ddgi_trace::record(device, pipelines, sc, fg, ddgi_res, env_cubemap, tlas_buf);
rhi::ComputePipelineDesc ddgi_update_irr;
ddgi_update_irr.device = &device;
ddgi_update_irr.set_shader(sc, "src/passes/ddgi/ddgi_update_irradiance.slang", "csMain");
pipelines.add_compute("DdgiUpdateIrr", ddgi_update_irr);
ddgi_update::record(
fg, ddgi_res, ddgi_res.irradiance_img, pipelines.get_compute("DdgiUpdateIrr"), "Ddgi Update Irradiance"
);
rhi::ComputePipelineDesc ddgi_update_dist;
ddgi_update_dist.device = &device;
ddgi_update_dist.set_shader(sc, "src/passes/ddgi/ddgi_update_distance.slang", "csMain");
pipelines.add_compute("DdgiUpdateDist", ddgi_update_dist);
ddgi_update::record(
fg, ddgi_res, ddgi_res.distance_img, pipelines.get_compute("DdgiUpdateDist"), "Ddgi Update Distance"
);
// NOTE: silently ordered, relocate writes the probe offset, then classify
// reads that offset and the fixed-ray backface ratio to ease the probe state
ddgi_relocate::record(device, pipelines, sc, fg, ddgi_res);
ddgi_classify::record(device, pipelines, sc, fg, ddgi_res);
ddgi_probes::record(device, pipelines, sc, fg, forward.hdr, forward.depth, ddgi_res, &render_data.debug);
}
hiz::record(device, pipelines, sc, fg, hiz_res, forward.depth);
// ssr::record(device, pipelines, sc, fg, ssr_res, forward.hdr, dn.depth, dn.normal, hiz_res.hiz);
skybox::record(device, pipelines, sc, fg, forward.hdr, forward.depth, env_cubemap);
grid::record(device, pipelines, sc, fg, forward.hdr, forward.depth);
#ifdef ENABLE_EDITOR
debug_lines::record(
device, pipelines, sc, fg, forward.hdr, forward.depth, debug_lines::Settings{.draw = &render_data.debug_draw}
);
ImageHandle display_target =
fg.add_image({.desc = {.format = rhi::swapchain_format(swapchain)}, .name = "fg/present/display"});
ImageHandle id_blit_target = id_blit::record(
device, pipelines, sc, fg, main_bd.indirect, main_bd.draw_count, main_bd.visible, forward.depth
);
#endif
ImageHandle swapchain_target = fg.import_image(rhi::swapchain_image(swapchain, 0), rhi::ResourceState::ColorDraw);
fullscreen_fg::record(
device,
pipelines,
sc,
fg,
forward.hdr,
forward.depth,
rhi::swapchain_format(swapchain),
#ifdef ENABLE_EDITOR
display_target
#else
swapchain_target
#endif
// , ssr_res.output
);
#ifdef ENABLE_EDITOR
editor::PresentTargets present_targets{};
present_targets.display = display_target;
present_targets.id_blit = id_blit_target;
present_targets.irradiance = ddgi_res.irradiance_img;
present_targets.distance = ddgi_res.distance_img;
for (u32 i = 0; i < Light::CASCADE_COUNT; ++i) {
present_targets.cascade[i] = shadow_map.cascade[i];
}
present_targets.swapchain = swapchain_target;
editor::record_present(editor, pipelines, sc, fg, swapchain, present_targets, pass_timings);
#endif
fg.compile(device, render_size);
#ifdef ENABLE_EDITOR
pass_timings.enable(fg, device, MAX_FRAMES_IN_FLIGHT);
#endif
while (platform_update(platform)) {
VEL_PROFILE_FRAME();
render_data.frame_stats.record((f32)(platform.raw_dt * 1000.0));
const u32 frame_slot = frame_count % MAX_FRAMES_IN_FLIGHT;
const u64 frame_wait_value =
(frame_count + 1 > MAX_FRAMES_IN_FLIGHT) ? (frame_count + 1 - MAX_FRAMES_IN_FLIGHT) : 0;
if (frame_wait_value != 0 && !rhi::sync_host_wait(frame_sync, frame_wait_value)) {
// GPU wedged (device lost / timeout)
VEL_CRITICAL("FRAME SYNC WAIT MISS");
rhi::device_wait_idle(device);
}
FrameData &frame = frame_data[frame_slot];
bool recreate_swap = platform.framebuffer_resized || rhi::swapchain_needs_recreate(swapchain);
platform.framebuffer_resized = false;
if (recreate_swap) {
if (!platform_wait_for_valid_framebuffer(platform)) {
continue;
}
rhi::queue_wait_idle(device);
destroy_swapchain(device, swapchain);
rhi::SwapchainDesc swapchain_desc{};
swapchain_desc.native_window = platform_native_window(platform);
swapchain_desc.width = platform.width;
swapchain_desc.height = platform.height;
swapchain_desc.vsync_enabled = config.vsync_enabled;
swapchain_desc.image_count = config.swapchain_image_count;
create_swapchain(device, swapchain_desc, swapchain);
#ifdef ENABLE_EDITOR
editor::eval_render_size(editor.viewport_width, editor.viewport_height, swapchain, render_size);
#else
render_size = rhi::swapchain_extent(swapchain);
#endif
fg.overwrite_imported_image(swapchain_target, rhi::swapchain_image(swapchain, 0));
fg.compile(device, render_size);
Camera &cam = scene.cameras[scene.active_camera];
cam.aspect = static_cast<f32>(render_size.width) / static_cast<f32>(render_size.height);
rhi::Extent2D sc_extent = rhi::swapchain_extent(swapchain);
platform.width = sc_extent.width;
platform.height = sc_extent.height;
continue;
}
#ifdef ENABLE_EDITOR
editor::handle_frame_begin(
editor,
render_data,
device,
swapchain,
fg,
swapchain_target,
render_size,
scene,
pipelines,
pass_timings,
frame_slot
);
#endif
Camera &cam = scene.cameras[scene.active_camera];
cam.position = scene.scene_graph.translations[cam.node];
cam.aspect = static_cast<f32>(render_size.width) / static_cast<f32>(render_size.height);
cam.aspect = static_cast<f32>(render_size.width) / static_cast<f32>(render_size.height);
update_camera(cam, platform.input);
scene.scene_graph.set_translation(cam.node, cam.position);
update_light(scene.directional_lights[0], scene.cameras[scene.active_camera]);
update_light_cascades(scene.directional_lights[0], scene.cameras[scene.active_camera], cascade_sizes);
build_ubo(scene, render_data, device, frame, render_size);
scene.evaluate();
upload_scene_data(scene, frame);
#ifdef ENABLE_EDITOR
render_data.debug_draw.clear();
editor::apply_debug_settings(editor, render_data.debug_draw);
if (editor.show_debug_lines) {
ddgi_trace::debug_draw(render_data.debug_draw, render_data);
editor::debug_draw_selection(render_data.debug_draw, scene, render_data, editor.selected_node);
editor::update_gizmo(editor, scene, render_data.debug_draw);
}
#endif
rhi::reset_cmd_pool(device, frame_pools[frame_slot]);
rhi::begin_cmd_buffer(
frame_cmds[frame_slot], rhi::CmdBufferBeginDesc{.usage = rhi::CmdBufferUsage::OneTimeSubmit}
);
u32 image_index;
if (!rhi::acquire_swapchain_image(device, swapchain, image_index, frame_cmds[frame_slot])) {
rhi::reset_cmd_buffer(frame_cmds[frame_slot]);
continue;
}
rhi::Image &swap_image = *rhi::swapchain_image(swapchain, image_index);
fg.overwrite_imported_image(swapchain_target, &swap_image);
fg.execute(
frame_cmds[frame_slot],
GraphExecInfo{
.frame_data = frame_data,
.render_data = &render_data,
.scene = &scene,
.frame_index = frame_slot,
.frame_count = frame_count,
}
);
{
rhi::ImageRange range{};
rhi::barrier(frame_cmds[frame_slot], swap_image, range, swap_image.state, rhi::ResourceState::Display);
}
rhi::end_cmd_buffer(frame_cmds[frame_slot]);
rhi::QueueSignal frame_signal{{frame_sync, frame_count + 1}};
rhi::QueueSubmitDesc submit_desc{};
submit_desc.cmd_count = 1;
submit_desc.cmds = &frame_cmds[frame_slot];
submit_desc.signal_count = 1;
submit_desc.signals = &frame_signal;
rhi::queue_submit(device, rhi::graphics_queue(device), submit_desc);
rhi::present_swapchain(device, swapchain, image_index, {frame_sync, frame_count + 1});
frame_count++;
}
rhi::device_wait_idle(device);
#ifdef ENABLE_EDITOR
pass_timings.destroy(device);
#endif
fg.destroy();
#ifdef ENABLE_EDITOR
editor::shutdown(editor, device);
#endif
destroy_rt_scene(device, render_data);
pipelines.clear();
destroy_default_textures(device, textures);
rhi::destroy_image_view(device, render_data.sky_view);
rhi::destroy_image(device, render_data.sky);
rhi::sync_destroy(frame_sync);
frame_sync = nullptr;
for (u32 i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
rhi::destroy_cmd_buffer(device, frame_pools[i], frame_cmds[i]);
rhi::destroy_cmd_pool(device, frame_pools[i]);
}
destroy_swapchain(device, swapchain);
qs_shutdown(quick_submit);
auto destroy_buf = [&](rhi::Buffer &b) {
if (rhi::valid(b)) {
rhi::destroy_buffer(device, b);
}
};
auto destroy_img = [&](rhi::Image &i) {
if (rhi::valid(i)) {
rhi::destroy_image(device, i);
}
};
auto destroy_iv = [&](rhi::ImageView &v) {
if (rhi::valid(v)) {
rhi::destroy_image_view(device, v);
}
};
auto destroy_view = [&](rhi::ImageView &v) { rhi::destroy_image_view(device, v); };
destroy_view(scene.env.environment_view);
destroy_img(scene.env.environment_img);
destroy_view(scene.env.irradiance_view);
destroy_img(scene.env.irradiance_img);
destroy_view(scene.env.prefiltered_view);
destroy_img(scene.env.prefiltered_img);
destroy_view(scene.env.brdf_lut_view);
destroy_img(scene.env.brdf_lut_img);
for (auto &[img, view] : render_data.loaded_textures) {
destroy_iv(view);
destroy_img(img);
}
render_data.loaded_textures.clear();
rhi::destroy_buffer_view(device, render_data.vertex_view);
for (u32 i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
rhi::destroy_buffer_view(device, render_data.frame_ubo_view[i]);
}
destroy_buf(render_data.vertex_buffer);
destroy_buf(render_data.index_buffer);
destroy_buf(render_data.submesh_buffer);
destroy_buf(render_data.material_buffer);
destroy_buf(render_data.render_item_buffer);
for (u32 i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
destroy_buf(frame_data[i].frame_ubo);
destroy_buf(frame_data[i].point_light_buffer);
destroy_buf(frame_data[i].transform_buffer);
}
destroy_arena(device, render_data.staging);
destroy_memory_allocator(device);
destroy_window(device, platform);
vel::Logger::shutdown();
return 0;
}