scene.cpp

cross platform rendering playground

src/backend/conformance/scene/scene.cpp

13.15 KB
#include "backend/rhi.h"
#include "core/logger.h"
#include "core/math_rtm.h"
#include "core/platform.h"

#include <chrono>
#include <cmath>
#include <cstring>

struct SceneVertex {
    f32 pos[3];
    f32 nrm[3];
    f32 uv[2];
};

// Unit cube, 24 verts (split per face for flat normals + full-quad uvs).
static constexpr SceneVertex SCENE_VERTS[24] = {
    // +Z front
    {{-0.5f, -0.5f, 0.5f}, {0, 0, 1}, {0, 0}},
    {{0.5f, -0.5f, 0.5f}, {0, 0, 1}, {1, 0}},
    {{0.5f, 0.5f, 0.5f}, {0, 0, 1}, {1, 1}},
    {{-0.5f, 0.5f, 0.5f}, {0, 0, 1}, {0, 1}},
    // -Z back
    {{0.5f, -0.5f, -0.5f}, {0, 0, -1}, {0, 0}},
    {{-0.5f, -0.5f, -0.5f}, {0, 0, -1}, {1, 0}},
    {{-0.5f, 0.5f, -0.5f}, {0, 0, -1}, {1, 1}},
    {{0.5f, 0.5f, -0.5f}, {0, 0, -1}, {0, 1}},
    // +X right
    {{0.5f, -0.5f, 0.5f}, {1, 0, 0}, {0, 0}},
    {{0.5f, -0.5f, -0.5f}, {1, 0, 0}, {1, 0}},
    {{0.5f, 0.5f, -0.5f}, {1, 0, 0}, {1, 1}},
    {{0.5f, 0.5f, 0.5f}, {1, 0, 0}, {0, 1}},
    // -X left
    {{-0.5f, -0.5f, -0.5f}, {-1, 0, 0}, {0, 0}},
    {{-0.5f, -0.5f, 0.5f}, {-1, 0, 0}, {1, 0}},
    {{-0.5f, 0.5f, 0.5f}, {-1, 0, 0}, {1, 1}},
    {{-0.5f, 0.5f, -0.5f}, {-1, 0, 0}, {0, 1}},
    // +Y top
    {{-0.5f, 0.5f, 0.5f}, {0, 1, 0}, {0, 0}},
    {{0.5f, 0.5f, 0.5f}, {0, 1, 0}, {1, 0}},
    {{0.5f, 0.5f, -0.5f}, {0, 1, 0}, {1, 1}},
    {{-0.5f, 0.5f, -0.5f}, {0, 1, 0}, {0, 1}},
    // -Y bottom
    {{-0.5f, -0.5f, -0.5f}, {0, -1, 0}, {0, 0}},
    {{0.5f, -0.5f, -0.5f}, {0, -1, 0}, {1, 0}},
    {{0.5f, -0.5f, 0.5f}, {0, -1, 0}, {1, 1}},
    {{-0.5f, -0.5f, 0.5f}, {0, -1, 0}, {0, 1}},
};
static constexpr u32 SCENE_INDICES[36] = {
    0,  1,  2,  2,  3,  0,  // +Z
    4,  5,  6,  6,  7,  4,  // -Z
    8,  9,  10, 10, 11, 8,  // +X
    12, 13, 14, 14, 15, 12, // -X
    16, 17, 18, 18, 19, 16, // +Y
    20, 21, 22, 22, 23, 20, // -Y
};

static constexpr u32 SCENE_TEX_W = 4;
static constexpr u32 SCENE_TEX_H = 4;
// Quadrant-asymmetric checker: TL red, TR green, BL blue, BR white.
static constexpr u8 SCENE_TEX[SCENE_TEX_W * SCENE_TEX_H * 4] = {
    255, 0,   0,   255, 255, 0,   0,   255, 0, 255, 0,   255, 0,   255, 0,   255, 255, 0,   0,   255, 255, 0,
    0,   255, 0,   255, 0,   255, 0,   255, 0, 255, 0,   0,   255, 255, 0,   0,   255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 0,   0,   255, 255, 0, 0,   255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
};

struct ScenePush {
    mat4 viewProj; // 0
    u64 tex;       // 64, rhi::handle_id
    u64 samp;      // 72, rhi::handle_id
    vec4 lightDir; // 80, xyz = direction TO light
};
static_assert(sizeof(ScenePush) == 96, "ScenePush size drift");

int main() {
    vel::Logger::init();
    rhi::Config config{};
    config.vsync_enabled = true;
    config.enable_validation = true;
    rhi::Device device{};
    rhi::Swapchain swapchain{};
    rhi::CmdPool pool{};
    rhi::CmdBuffer cmd{};
    rhi::Sync *frame_sync = nullptr;
    rhi::ShaderCompiler sc{};
    rhi::RenderPipeline pipeline{};
    Platform platform{};
    platform.width = 800;
    platform.height = 800;

    init_platform(config.instance_extensions);
    rhi::create_context(device, config);
    rhi::create_memory_allocator(device);
    rhi::init_compiler(sc);
    create_window(device, platform, "scene", 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;
    rhi::create_swapchain(device, swapchain_desc, swapchain);

    rhi::Queue graphics_queue = rhi::graphics_queue(device);
    rhi::CmdPoolDesc cmd_pool_desc;
    cmd_pool_desc.queue_family_index = rhi::queue_family_index(graphics_queue);
    cmd_pool_desc.flags = rhi::CmdPoolUsage::Transient | rhi::CmdPoolUsage::ResetCommandBuffer;
    rhi::create_cmd_pool(device, cmd_pool_desc, pool);
    rhi::CmdBufferDesc cmd_desc{};
    cmd_desc.pool = &pool;
    cmd_desc.level = rhi::CmdBufferLevel::Primary;
    rhi::create_cmd_buffer(device, cmd_desc, cmd);
    frame_sync = rhi::sync_create(device, 0);

    rhi::Buffer vertex_buffer{}, index_buffer{}, staging{};
    {
        rhi::BufferDesc d{};
        d.size = sizeof(SCENE_VERTS);
        d.usage = rhi::BufferUsage::VertexBuffer;
        d.memory = rhi::BufferMemType::Upload;
        rhi::create_buffer(device, d, vertex_buffer);
        std::memcpy(vertex_buffer.mapped, SCENE_VERTS, sizeof(SCENE_VERTS));
    }
    {
        rhi::BufferDesc d{};
        d.size = sizeof(SCENE_INDICES);
        d.usage = rhi::BufferUsage::IndexBuffer;
        d.memory = rhi::BufferMemType::Upload;
        rhi::create_buffer(device, d, index_buffer);
        std::memcpy(index_buffer.mapped, SCENE_INDICES, sizeof(SCENE_INDICES));
    }
    {
        rhi::BufferDesc d{};
        d.size = SCENE_TEX_W * SCENE_TEX_H * 4;
        d.usage = rhi::BufferUsage::CopySrc;
        d.memory = rhi::BufferMemType::Upload;
        rhi::create_buffer(device, d, staging);
        std::memcpy(staging.mapped, SCENE_TEX, sizeof(SCENE_TEX));
    }

    rhi::Sampler sampler{};
    {
        rhi::SamplerDesc sdesc{};
        sdesc.filter = rhi::SamplerFilter::LINEAR;
        sdesc.address = rhi::SamplerAddressMode::CLAMP;
        rhi::create_sampler(device, sdesc, sampler);
    }

    rhi::Image tex_img{};
    rhi::ImageView tex_view{};
    rhi::Image depth_img{};
    rhi::ImageView depth_view{};
    auto create_depth = [&](u32 w, u32 h) {
        rhi::ImageDesc idesc{};
        idesc.width = w;
        idesc.height = h;
        idesc.format = rhi::ImageFormat::D32_FLOAT;
        idesc.usage = rhi::ImageUsage::DepthAttachment | rhi::ImageUsage::Sampled;
        rhi::create_image(device, idesc, depth_img);
        rhi::ImageViewDesc vdesc{};
        vdesc.image = &depth_img;
        vdesc.aspect = rhi::ImageAspect::Depth;
        rhi::create_image_view(device, vdesc, depth_view);
    };
    create_depth(platform.width, platform.height);
    {
        rhi::reset_cmd_buffer(cmd);
        rhi::begin_cmd_buffer(cmd, rhi::CmdBufferBeginDesc{.usage = rhi::CmdBufferUsage::OneTimeSubmit});
        rhi::ImageDesc idesc{};
        idesc.width = SCENE_TEX_W;
        idesc.height = SCENE_TEX_H;
        idesc.format = rhi::ImageFormat::RGBA8_UNORM;
        idesc.usage = rhi::ImageUsage::Sampled | rhi::ImageUsage::TransferDst;
        rhi::create_image(device, idesc, tex_img);
        rhi::ImageViewDesc vdesc{};
        vdesc.image = &tex_img;
        vdesc.aspect = rhi::ImageAspect::Color;
        rhi::create_image_view(device, vdesc, tex_view);
        rhi::ImageRange range{};
        rhi::barrier(cmd, tex_img, range, rhi::ResourceState::Idle, rhi::ResourceState::TransferTo);
        rhi::BufferTextureCopyRegion region{};
        region.layer_count = 1;
        region.texture_extent_width = SCENE_TEX_W;
        region.texture_extent_height = SCENE_TEX_H;
        region.texture_extent_depth = 1;
        rhi::copy_buffer_to_texture(cmd, staging, tex_view, region);
        rhi::barrier(cmd, tex_img, range, rhi::ResourceState::TransferTo, rhi::ResourceState::TextureSample);
        tex_img.state = rhi::ResourceState::TextureSample;
        rhi::end_cmd_buffer(cmd);
        rhi::QueueSubmitDesc submit{};
        submit.cmd_count = 1;
        submit.cmds = &cmd;
        rhi::queue_submit(device, graphics_queue, submit);
        rhi::queue_wait_idle(device);
    }

    rhi::RenderPipelineDesc pd{};
    pd.device = &device;
    pd.set_shaders(sc, "conformance/scene.slang", "vsMain", "fsMain");
    pd.add_vertex_binding(0, sizeof(SceneVertex));
    pd.add_vertex_attribute(0, 0, rhi::ImageFormat::RGB32_FLOAT, offsetof(SceneVertex, pos));
    pd.add_vertex_attribute(1, 0, rhi::ImageFormat::RGB32_FLOAT, offsetof(SceneVertex, nrm));
    pd.add_vertex_attribute(2, 0, rhi::ImageFormat::RG32_FLOAT, offsetof(SceneVertex, uv));
    pd.set_depth_testing(true, true, rhi::PipelineCompareOp::GREATER);
    pd.set_depth_format(rhi::ImageFormat::D32_FLOAT);
    pd.set_cull_mode(rhi::PipelineCullMode::NONE, rhi::PipelineTriangleWindingOrder::CCW);
    pd.set_color_format(rhi::swapchain_format(swapchain));
    pipeline = pd.build();
    if (!rhi::valid(pipeline)) {
        VEL_CRITICAL("scene: pipeline build failed");
        return 1;
    }

    ScenePush push{};
    {
        const mat4 view = look_at(vec3(1.6f, 1.3f, -2.4f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 1.0f, 0.0f));
        const mat4 proj = perspective(radians(60.0f), 1.0f, 0.01f, 1000.0f);
        push.viewProj = view * proj; // row-vector engine convention: shaders consume vec * mat
        push.tex = rhi::handle_id(device, tex_view);
        push.samp = rhi::handle_id(device, sampler);
        push.lightDir = vec4(0.4f, 0.8f, -0.45f, 0.0f);
    }

    u64 frame_count = 0;
    while (platform_update(platform)) {
        if (platform.framebuffer_resized || rhi::swapchain_needs_recreate(swapchain)) {
            platform.framebuffer_resized = false;
            if (!platform_wait_for_valid_framebuffer(platform)) {
                break;
            }
            rhi::queue_wait_idle(device);
            rhi::destroy_swapchain(device, swapchain);
            swapchain_desc.width = platform.width;
            swapchain_desc.height = platform.height;
            rhi::create_swapchain(device, swapchain_desc, swapchain);
            rhi::destroy_image_view(device, depth_view);
            rhi::destroy_image(device, depth_img);
            create_depth(platform.width, platform.height);
            continue;
        }
        rhi::reset_cmd_buffer(cmd);
        rhi::begin_cmd_buffer(cmd, rhi::CmdBufferBeginDesc{.usage = rhi::CmdBufferUsage::OneTimeSubmit});
        u32 image_index = 0;
        if (!rhi::acquire_swapchain_image(device, swapchain, image_index, cmd)) {
            rhi::reset_cmd_buffer(cmd);
            continue;
        }
        rhi::Image *swap_image = rhi::swapchain_image(swapchain, image_index);
        rhi::Extent2D extent = rhi::swapchain_extent(swapchain);
        rhi::ImageView swap_view = rhi::get_cached_image_view(device, *swap_image, {.aspect = rhi::ImageAspect::Color});

        rhi::ImageRange depth_range{};
        depth_range.aspect = rhi::ImageAspect::Depth;
        if (depth_img.state != rhi::ResourceState::DepthDraw) {
            rhi::barrier(cmd, depth_img, depth_range, depth_img.state, rhi::ResourceState::DepthDraw);
        }

        rhi::AttachmentDesc color_att{};
        color_att.img = &swap_view;
        color_att.loadOp = rhi::LoadOp::CLEAR;
        color_att.storeOp = rhi::StoreOp::STORE;
        color_att.clearValue.color.float32[0] = 0.05f;
        color_att.clearValue.color.float32[1] = 0.05f;
        color_att.clearValue.color.float32[2] = 0.07f;
        color_att.clearValue.color.float32[3] = 1.0f;
        rhi::AttachmentDesc depth_att{};
        depth_att.img = &depth_view;
        depth_att.loadOp = rhi::LoadOp::CLEAR;
        depth_att.storeOp = rhi::StoreOp::STORE;
        depth_att.clearValue.depthStencil = {0.0f, 0};
        rhi::RenderingInfo ri{};
        ri.colorAttachments[0] = color_att;
        ri.colorAttachmentCount = 1;
        ri.depthAttachment = depth_att;
        ri.width = extent.width;
        ri.height = extent.height;

        rhi::begin_rendering(cmd, ri);
        rhi::set_pipeline(cmd, pipeline);
        rhi::Viewport vp{};
        vp.width = (f32)extent.width;
        vp.height = (f32)extent.height;
        rhi::set_viewports(cmd, &vp);
        rhi::Rect scissor{};
        scissor.width = (i16)extent.width;
        scissor.height = (i16)extent.height;
        rhi::set_scissors(cmd, &scissor);
        rhi::set_vertex_buffer(cmd, vertex_buffer, 0, 0);
        rhi::set_index_buffer(cmd, index_buffer, 0, rhi::IndexType::UINT32);
        rhi::set_constants(cmd, pipeline, rhi::ShaderStage::VERTEX | rhi::ShaderStage::FRAGMENT, sizeof(push), &push);
        rhi::draw_indexed(cmd, 36, 1, 0, 0, 0);
        rhi::end_rendering(cmd);

        rhi::ImageRange range{};
        rhi::barrier(cmd, *swap_image, range, swap_image->state, rhi::ResourceState::Display);
        rhi::end_cmd_buffer(cmd);

        rhi::QueueSignal completion{{frame_sync, frame_count + 1}};
        rhi::QueueSubmitDesc submit{};
        submit.cmd_count = 1;
        submit.cmds = &cmd;
        submit.signal_count = 1;
        submit.signals = &completion;
        rhi::queue_submit(device, graphics_queue, submit);
        rhi::present_swapchain(device, swapchain, image_index, {frame_sync, frame_count + 1});
        ++frame_count;
        rhi::sync_host_wait(frame_sync, frame_count);
    }

    rhi::device_wait_idle(device);
    rhi::destroy_pipeline(device, pipeline);
    rhi::destroy_buffer(device, staging);
    rhi::destroy_image_view(device, depth_view);
    rhi::destroy_image(device, depth_img);
    rhi::destroy_image_view(device, tex_view);
    rhi::destroy_image(device, tex_img);
    rhi::destroy_sampler(device, sampler);
    rhi::destroy_buffer(device, index_buffer);
    rhi::destroy_buffer(device, vertex_buffer);
    rhi::sync_destroy(frame_sync);
    rhi::destroy_cmd_buffer(device, pool, cmd);
    rhi::destroy_cmd_pool(device, pool);
    rhi::destroy_swapchain(device, swapchain);
    rhi::destroy_memory_allocator(device);
    destroy_window(device, platform);
    rhi::destroy_context(device);
    vel::Logger::shutdown();
    return 0;
}