diff options
| author | hachem <im@hachem.wtf> | 2026-08-23 17:21:37 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-08-23 17:21:37 +0200 |
| commit | 3fd33ecff7472e4d6fc9e6b3905f56982e497ef2 (patch) | |
| tree | 66e5812f1a18b926e88a0dd549fe14e7c7327165 /src/rendering | |
| parent | e3aa33fc9a99d8b817bda42a567a4c347e18ab32 (diff) | |
[feat]: complete RHI api
Diffstat (limited to 'src/rendering')
| -rw-r--r-- | src/rendering/black_hole_renderer.cpp | 144 | ||||
| -rw-r--r-- | src/rendering/black_hole_renderer.h | 55 | ||||
| -rw-r--r-- | src/rendering/framebuffer.cpp | 251 | ||||
| -rw-r--r-- | src/rendering/framebuffer.h | 80 | ||||
| -rw-r--r-- | src/rendering/index_buffer.cpp | 34 | ||||
| -rw-r--r-- | src/rendering/index_buffer.h | 23 | ||||
| -rw-r--r-- | src/rendering/render_api.h | 25 | ||||
| -rw-r--r-- | src/rendering/renderer.cpp | 168 | ||||
| -rw-r--r-- | src/rendering/renderer.h | 155 | ||||
| -rw-r--r-- | src/rendering/renderer_backend.h | 53 | ||||
| -rw-r--r-- | src/rendering/rhi.h (renamed from src/rendering/rhi/rhi.h) | 17 | ||||
| -rw-r--r-- | src/rendering/scene_renderer.cpp | 217 | ||||
| -rw-r--r-- | src/rendering/scene_renderer.h | 56 | ||||
| -rw-r--r-- | src/rendering/texture_manager.cpp | 34 | ||||
| -rw-r--r-- | src/rendering/texture_manager.h | 19 | ||||
| -rw-r--r-- | src/rendering/uniform_buffer.cpp | 36 | ||||
| -rw-r--r-- | src/rendering/uniform_buffer.h | 24 | ||||
| -rw-r--r-- | src/rendering/vertex_array.cpp | 61 | ||||
| -rw-r--r-- | src/rendering/vertex_array.h | 35 | ||||
| -rw-r--r-- | src/rendering/vertex_buffer.cpp | 50 | ||||
| -rw-r--r-- | src/rendering/vertex_buffer.h | 78 |
21 files changed, 512 insertions, 1103 deletions
diff --git a/src/rendering/black_hole_renderer.cpp b/src/rendering/black_hole_renderer.cpp new file mode 100644 index 0000000..46b002c --- /dev/null +++ b/src/rendering/black_hole_renderer.cpp @@ -0,0 +1,144 @@ +#include "black_hole_renderer.h" + +#include "core/log.h" +#include "core/settings_manager.h" + +#include <algorithm> +#include <cstdint> +#include <cstring> + +namespace Donut +{ + using namespace RHI; + + namespace + { + constexpr float SagA_rs = 1.269e10f; // Schwarzschild radius of the modelled hole + + struct CamUBO { + glm::vec3 pos; float p0; glm::vec3 right; float p1; + glm::vec3 up; float p2; glm::vec3 fwd; float p3; + float tan_half_fov; float aspect; uint32_t moving; int p4; + }; + struct SimUBO { int steps_moving; int steps_static; float early_exit; float time; }; + } + + auto BlackHoleRenderer::init(RHI::Device& device) -> bool + { + m_device = &device; + + m_geo_lo = device.create_render_target(GEO_LO_W, GEO_LO_H, Format::RGBA8, false, Filter::Linear); + m_geo_hi = device.create_render_target(GEO_HI_W, GEO_HI_H, Format::RGBA8, false, Filter::Linear); + + // Fullscreen quad shared by the geodesic and present passes: pos.xy + uv. + const float quad[] = { + -1.f, 1.f, 0.f, 1.f, -1.f, -1.f, 0.f, 0.f, 1.f, -1.f, 1.f, 0.f, + -1.f, 1.f, 0.f, 1.f, 1.f, -1.f, 1.f, 0.f, 1.f, 1.f, 1.f, 1.f, + }; + m_quad_vb = device.create_buffer(BufferType::Vertex, sizeof(quad), quad); + + m_cam_ubo = device.create_buffer(BufferType::Uniform, 128); + m_disk_ubo = device.create_buffer(BufferType::Uniform, 32); + m_obj_ubo = device.create_buffer(BufferType::Uniform, 800); + m_sim_ubo = device.create_buffer(BufferType::Uniform, 16); + + // The single "object" is the black hole itself (position 0, radius = r_s). + std::vector<uint8_t> obj(800, 0); + int num_objects = 1; std::memcpy(obj.data() + 0, &num_objects, 4); + float pos_radius[4] = { 0, 0, 0, SagA_rs }; std::memcpy(obj.data() + 16, pos_radius, 16); + float color[4] = { 0, 0, 0, 1 }; std::memcpy(obj.data() + 272, color, 16); + m_obj_ubo->update(obj.data(), obj.size()); + + { + PipelineDesc d; + d.shader = "geodesic"; + d.vertex_layout = { 16, { { 0, 2, 0 }, { 1, 2, 8 } } }; + d.resources = { + { ResourceKind::UniformBuffer, 0, "Camera" }, + { ResourceKind::UniformBuffer, 1, "Disk" }, + { ResourceKind::UniformBuffer, 2, "Objects" }, + { ResourceKind::UniformBuffer, 3, "Simulation" }, + { ResourceKind::Texture, 4, "u_HDRIEnvironment" }, + }; + d.topology = Topology::Triangles; + d.has_depth = false; // color-only off-screen target + m_geo_pipeline = device.create_pipeline(d); + } + { + PipelineDesc d; + d.shader = "textured_quad"; + d.vertex_layout = { 16, { { 0, 2, 0 }, { 1, 2, 8 } } }; + d.resources = { { ResourceKind::Texture, 0, "u_ScreenTexture" } }; + d.topology = Topology::Triangles; + d.has_depth = true; // swapchain target carries depth (unused here) + m_present_pipeline = device.create_pipeline(d); + } + + DONUT_INFO("BlackHoleRenderer ready ({}x{} moving / {}x{} settled)", + GEO_LO_W, GEO_LO_H, GEO_HI_W, GEO_HI_H); + return true; + } + + auto BlackHoleRenderer::render_geodesic(RHI::CommandList& cmd, const GeodesicView& view, + const BlackHoleParams& params, RHI::Texture* cubemap) -> void + { + CamUBO cam{}; + cam.pos = view.position; cam.right = view.right; cam.up = view.up; cam.fwd = view.forward; + cam.tan_half_fov = view.tan_half_fov; + cam.aspect = view.aspect; + cam.moving = view.moving ? 1u : 0u; + m_cam_ubo->update(&cam, sizeof(cam)); + + // Same integration budget whether moving or settled (the disk vanishes at + // steep poses below ~15000 steps); responsiveness comes from the lower-res + // target instead. Sourced from the UI, clamped GPU-safe. + constexpr int kStepCeil = 15000; + SimUBO sim{}; + sim.steps_static = std::clamp(params.quality_steps, 1000, kStepCeil); + sim.steps_moving = sim.steps_static; + sim.early_exit = SettingsManager::get_early_exit_distance(); + sim.time = view.time; + m_sim_ubo->update(&sim, sizeof(sim)); + + // Disk struct: r_in, r_out, turbulence, slab half-thickness, brightness, + // temperature (radii in Schwarzschild radii). + float disk[8] = { + std::max(params.disk_inner_rs, 3.0f) * SagA_rs, + std::max(params.disk_outer_rs, params.disk_inner_rs + 0.5f) * SagA_rs, + std::max(params.turbulence, 0.0f), + SagA_rs * 0.1f, + std::max(params.brightness, 0.0f), + std::max(params.temperature, 1000.0f), + 0.0f, 0.0f, + }; + m_disk_ubo->update(disk, sizeof(disk)); + + // Progressive resolution: small target while moving, large once settled. + RenderTarget* target = view.moving ? m_geo_lo.get() : m_geo_hi.get(); + m_last_target = target; + + cmd.begin_render_pass(target, glm::vec4(0, 0, 0, 1)); + cmd.set_viewport(0, 0, target->width(), target->height(), false); + cmd.bind_pipeline(m_geo_pipeline.get()); + cmd.bind_uniform(0, m_cam_ubo.get()); + cmd.bind_uniform(1, m_disk_ubo.get()); + cmd.bind_uniform(2, m_obj_ubo.get()); + cmd.bind_uniform(3, m_sim_ubo.get()); + cmd.bind_texture(4, cubemap); + cmd.bind_vertex_buffer(m_quad_vb.get()); + cmd.draw(6); + cmd.end_render_pass(); + } + + auto BlackHoleRenderer::blit(RHI::CommandList& cmd, int fb_width, int fb_height) -> void + { + if (!m_last_target) return; + // flip_y matches the shared top-left orientation (Vulkan flips via a + // negative-height viewport; GL is a no-op). + cmd.set_viewport(0, 0, fb_width, fb_height, true); + cmd.bind_pipeline(m_present_pipeline.get()); + cmd.bind_texture(0, m_last_target->color_texture()); + cmd.bind_vertex_buffer(m_quad_vb.get()); + cmd.draw(6); + } +} diff --git a/src/rendering/black_hole_renderer.h b/src/rendering/black_hole_renderer.h new file mode 100644 index 0000000..b9c158b --- /dev/null +++ b/src/rendering/black_hole_renderer.h @@ -0,0 +1,55 @@ +#pragma once + +#include "rhi.h" +#include "renderer_backend.h" // BlackHoleParams +#include <glm/glm.hpp> + +namespace Donut +{ + // The black-hole view, written ONCE against the RHI. Each frame it ray-traces + // the geodesic shader into an off-screen target (progressive resolution: a + // small target while the camera moves, a larger one once it settles) and then + // upscales that target onto the swapchain. The Application owns the camera and + // the HDRI cubemap and passes them in. + struct GeodesicView + { + glm::vec3 position, right, up, forward; + float tan_half_fov = 1.0f; + float aspect = 16.0f / 9.0f; + bool moving = false; + float time = 0.0f; + }; + + class BlackHoleRenderer + { + public: + static constexpr int GEO_LO_W = 480, GEO_LO_H = 270; + static constexpr int GEO_HI_W = 960, GEO_HI_H = 540; + + auto init(RHI::Device& device) -> bool; + + // Off-screen geodesic pass (opens/closes its own render pass). Call before + // the swapchain pass; `cubemap` is the shared HDRI environment. + auto render_geodesic(RHI::CommandList& cmd, const GeodesicView& view, + const BlackHoleParams& params, RHI::Texture* cubemap) -> void; + + // Upscales the geodesic target onto the swapchain (call inside the open + // swapchain render pass, before ImGui). + auto blit(RHI::CommandList& cmd, int fb_width, int fb_height) -> void; + + private: + RHI::Device* m_device = nullptr; + + Ref<RHI::Pipeline> m_geo_pipeline; + Ref<RHI::RenderTarget> m_geo_lo; + Ref<RHI::RenderTarget> m_geo_hi; + Ref<RHI::Buffer> m_quad_vb; + Ref<RHI::Buffer> m_cam_ubo; + Ref<RHI::Buffer> m_disk_ubo; + Ref<RHI::Buffer> m_obj_ubo; + Ref<RHI::Buffer> m_sim_ubo; + + Ref<RHI::Pipeline> m_present_pipeline; + RHI::RenderTarget* m_last_target = nullptr; // target chosen this frame + }; +} diff --git a/src/rendering/framebuffer.cpp b/src/rendering/framebuffer.cpp deleted file mode 100644 index 3da4b48..0000000 --- a/src/rendering/framebuffer.cpp +++ /dev/null @@ -1,251 +0,0 @@ -#include "framebuffer.h" -#include "core/log.h" - -#include <glad/glad.h> - -namespace Donut -{ - namespace Utils - { - static GLenum TextureTarget(bool multisampled) - { - return multisampled ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; - } - - static void bind_texture(bool multisampled, uint32_t id) - { - glBindTexture(TextureTarget(multisampled), id); - } - - static void CreateTextures(bool multisampled, uint32_t* out_id, uint32_t count) - { - // glCreateTextures is 4.5 DSA; macOS caps at 4.1. Callers bind each - // texture (with the correct target) before use. - glGenTextures(count, out_id); - } - - static void AttachColorTexture(uint32_t id, int samples, GLenum internal_format, GLenum format, uint32_t width, uint32_t height, int index) - { - bool multisampled = samples > 1; - if (multisampled) - { - glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, internal_format, width, height, GL_FALSE); - } - else - { - // Integer color formats require an integer pixel type even when - // data is null, or macOS's strict core profile rejects the call. - GLenum type = (format == GL_RED_INTEGER) ? GL_INT : GL_UNSIGNED_BYTE; - glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, type, nullptr); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + index, TextureTarget(multisampled), id, 0); - } - - static void AttachDepthTexture(uint32_t id, int samples, GLenum format, GLenum attachment_type, uint32_t width, uint32_t height) - { - bool multisampled = samples > 1; - if (multisampled) - { - glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, format, width, height, GL_FALSE); - } - else - { - // glTexStorage2D is 4.2; use mutable storage for macOS (4.1). - GLenum depth_format = (format == GL_DEPTH24_STENCIL8) ? GL_DEPTH_STENCIL : GL_DEPTH_COMPONENT; - GLenum depth_type = (format == GL_DEPTH24_STENCIL8) ? GL_UNSIGNED_INT_24_8 : GL_FLOAT; - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, depth_format, depth_type, nullptr); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - - glFramebufferTexture2D(GL_FRAMEBUFFER, attachment_type, TextureTarget(multisampled), id, 0); - } - - static bool IsDepthFormat(FramebufferTextureFormat format) - { - switch (format) - { - case FramebufferTextureFormat::DEPTH24STENCIL8: return true; - } - return false; - } - - static GLenum DonutFBTextureFormatToGL(FramebufferTextureFormat format) - { - switch (format) - { - case FramebufferTextureFormat::RGBA8: return GL_RGBA8; - case FramebufferTextureFormat::RED_INTEGER: return GL_RED_INTEGER; - } - - return 0; - } - } - - Framebuffer::Framebuffer(const FramebufferSpecification& spec) - : m_specification(spec) - { - for (auto spec : m_specification.attachments.attachments) - { - if (!Utils::IsDepthFormat(spec.texture_format)) - m_color_attachment_specifications.emplace_back(spec); - else - m_depth_attachment_specification = spec; - } - - invalidate(); - } - - Framebuffer::~Framebuffer() - { - glDeleteFramebuffers(1, &m_renderer_id); - glDeleteTextures(static_cast<GLsizei>(m_color_attachments.size()), m_color_attachments.data()); - glDeleteTextures(1, &m_depth_attachment); - } - - auto Framebuffer::invalidate() -> void - { - if (m_renderer_id) - { - glDeleteFramebuffers(1, &m_renderer_id); - glDeleteTextures(static_cast<GLsizei>(m_color_attachments.size()), m_color_attachments.data()); - glDeleteTextures(1, &m_depth_attachment); - - m_color_attachments.clear(); - m_depth_attachment = 0; - } - - glGenFramebuffers(1, &m_renderer_id); // glCreateFramebuffers is 4.5 DSA; unavailable on macOS 4.1 - glBindFramebuffer(GL_FRAMEBUFFER, m_renderer_id); - - bool multisample = m_specification.Samples > 1; - - if (m_color_attachment_specifications.size()) - { - m_color_attachments.resize(m_color_attachment_specifications.size()); - Utils::CreateTextures(multisample, m_color_attachments.data(), static_cast<uint32_t>(m_color_attachments.size())); - - for (size_t i = 0; i < m_color_attachments.size(); i++) - { - Utils::bind_texture(multisample, m_color_attachments[i]); - switch (m_color_attachment_specifications[i].texture_format) - { - case FramebufferTextureFormat::RGBA8: - Utils::AttachColorTexture(m_color_attachments[i], m_specification.Samples, GL_RGBA8, GL_RGBA, m_specification.Width, m_specification.Height, static_cast<int>(i)); - break; - case FramebufferTextureFormat::RED_INTEGER: - Utils::AttachColorTexture(m_color_attachments[i], m_specification.Samples, GL_R32I, GL_RED_INTEGER, m_specification.Width, m_specification.Height, static_cast<int>(i)); - break; - } - } - } - - if (m_depth_attachment_specification.texture_format != FramebufferTextureFormat::None) - { - Utils::CreateTextures(multisample, &m_depth_attachment, 1); - Utils::bind_texture(multisample, m_depth_attachment); - switch (m_depth_attachment_specification.texture_format) - { - case FramebufferTextureFormat::DEPTH24STENCIL8: - Utils::AttachDepthTexture(m_depth_attachment, m_specification.Samples, GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT, m_specification.Width, m_specification.Height); - break; - } - } - - if (m_color_attachments.size() > 1) - { - GLenum buffers[4] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 }; - glDrawBuffers(static_cast<GLsizei>(m_color_attachments.size()), buffers); - } - else if (m_color_attachments.empty()) - glDrawBuffer(GL_NONE); - - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - { - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - switch (status) - { - case GL_FRAMEBUFFER_UNDEFINED: - DONUT_ERROR("Framebuffer is undefined"); - break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - DONUT_ERROR("Framebuffer has incomplete attachment"); - break; - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - DONUT_ERROR("Framebuffer is missing attachment"); - break; - case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: - DONUT_ERROR("Framebuffer has incomplete draw buffer"); - break; - case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: - DONUT_ERROR("Framebuffer has incomplete read buffer"); - break; - case GL_FRAMEBUFFER_UNSUPPORTED: - DONUT_ERROR("Framebuffer format is unsupported"); - break; - case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: - DONUT_ERROR("Framebuffer has incomplete multisample"); - break; - case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: - DONUT_ERROR("Framebuffer has incomplete layer targets"); - break; - default: - DONUT_ERROR("Framebuffer is incomplete (unknown error: {})", status); - break; - } - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - } - - auto Framebuffer::bind() -> void - { - glBindFramebuffer(GL_FRAMEBUFFER, m_renderer_id); - glViewport(0, 0, m_specification.Width, m_specification.Height); - } - - auto Framebuffer::unbind() -> void - { - glBindFramebuffer(GL_FRAMEBUFFER, 0); - } - - auto Framebuffer::resize(uint32_t width, uint32_t height) -> void - { - m_specification.Width = width; - m_specification.Height = height; - - invalidate(); - } - - auto Framebuffer::read_pixel(uint32_t attachment_index, int x, int y) -> int - { - glReadBuffer(GL_COLOR_ATTACHMENT0 + attachment_index); - int pixel_data; - glReadPixels(x, y, 1, 1, GL_RED_INTEGER, GL_INT, &pixel_data); - return pixel_data; - } - - auto Framebuffer::clear_attachment(uint32_t attachment_index, int value) -> void - { - // glClearTexImage is 4.4 and unavailable on macOS. clear the integer - // attachment by binding this framebuffer and clearing its draw buffer. - glBindFramebuffer(GL_FRAMEBUFFER, m_renderer_id); - glClearBufferiv(GL_COLOR, static_cast<GLint>(attachment_index), &value); - } - - auto Framebuffer::create(const FramebufferSpecification& spec) -> Ref<Framebuffer> - { - return create_ref<Framebuffer>(spec); - } -}; diff --git a/src/rendering/framebuffer.h b/src/rendering/framebuffer.h deleted file mode 100644 index cf1fbef..0000000 --- a/src/rendering/framebuffer.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include "core/memory.h" -#include "texture.h" - -#include <vector> - -namespace Donut -{ - enum class FramebufferTextureFormat - { - None = 0, - - RGBA8, - RED_INTEGER, - - DEPTH24STENCIL8, - - Depth = DEPTH24STENCIL8 - }; - - struct FramebufferTextureSpecification - { - FramebufferTextureSpecification() = default; - FramebufferTextureSpecification(FramebufferTextureFormat format) - : texture_format(format) {} - - FramebufferTextureFormat texture_format = FramebufferTextureFormat::None; - }; - - struct FramebufferAttachmentSpecification - { - FramebufferAttachmentSpecification() = default; - FramebufferAttachmentSpecification(std::initializer_list<FramebufferTextureSpecification> attachments) - : attachments(attachments) {} - - std::vector<FramebufferTextureSpecification> attachments; - }; - - struct FramebufferSpecification - { - uint32_t Width = 0, Height = 0; - FramebufferAttachmentSpecification attachments; - uint32_t Samples = 1; - - bool SwapChainTarget = false; - }; - - class Framebuffer - { - public: - Framebuffer(const FramebufferSpecification& spec); - ~Framebuffer(); - - auto bind() -> void; - auto unbind() -> void; - - auto resize(uint32_t width, uint32_t height) -> void; - auto read_pixel(uint32_t attachment_index, int x, int y) -> int; - - auto clear_attachment(uint32_t attachment_index, int value) -> void; - auto get_color_attachment_renderer_id(uint32_t index = 0) const -> uint32_t { return m_color_attachments[index]; } - - auto get_specification() const -> const FramebufferSpecification& { return m_specification; } - - static auto create(const FramebufferSpecification& spec) -> Ref<Framebuffer>; - - private: - auto invalidate() -> void; - - uint32_t m_renderer_id = 0; - FramebufferSpecification m_specification; - - std::vector<FramebufferTextureSpecification> m_color_attachment_specifications; - FramebufferTextureSpecification m_depth_attachment_specification = FramebufferTextureFormat::None; - - std::vector<uint32_t> m_color_attachments; - uint32_t m_depth_attachment = 0; - }; -}; diff --git a/src/rendering/index_buffer.cpp b/src/rendering/index_buffer.cpp deleted file mode 100644 index 2c933e0..0000000 --- a/src/rendering/index_buffer.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "index_buffer.h" - -#include <glad/glad.h> - -namespace Donut -{ - IndexBuffer::IndexBuffer(const uint32_t* indices, uint32_t count) - : m_count(count) - { - glGenBuffers(1, &m_renderer_id); // glCreateBuffers is 4.5 DSA; unavailable on macOS 4.1 - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_renderer_id); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(uint32_t), indices, GL_STATIC_DRAW); - } - - IndexBuffer::~IndexBuffer() - { - glDeleteBuffers(1, &m_renderer_id); - } - - auto IndexBuffer::bind() const -> void - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_renderer_id); - } - - auto IndexBuffer::unbind() const -> void - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - } - - auto IndexBuffer::create(const uint32_t* indices, uint32_t count) -> IndexBuffer* - { - return new IndexBuffer(indices, count); - } -}; diff --git a/src/rendering/index_buffer.h b/src/rendering/index_buffer.h deleted file mode 100644 index 0b1b2b6..0000000 --- a/src/rendering/index_buffer.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include <cstdint> - -namespace Donut -{ - class IndexBuffer - { - public: - IndexBuffer(const uint32_t* indices, uint32_t count); - ~IndexBuffer(); - - auto bind() const -> void; - auto unbind() const -> void; - auto get_count() const -> uint32_t { return m_count; } - - static auto create(const uint32_t* indices, uint32_t count) -> IndexBuffer*; - - private: - uint32_t m_renderer_id = 0; - uint32_t m_count = 0; - }; -}; diff --git a/src/rendering/render_api.h b/src/rendering/render_api.h new file mode 100644 index 0000000..62742c9 --- /dev/null +++ b/src/rendering/render_api.h @@ -0,0 +1,25 @@ +#pragma once + +namespace Donut +{ + // Which graphics backend the app runs on, selected once at startup (from + // settings) before the window is created. The rendering itself goes through + // the RHI (rendering/rhi.h) and the shared renderers; this is only the + // backend selector the window + application branch on. + class RendererAPI + { + public: + enum class API + { + None = 0, + OpenGL = 1, + Vulkan = 2, + }; + + static auto get_api() -> API { return s_api; } + static auto set_api(API api) -> void { s_api = api; } + + private: + inline static API s_api = API::None; + }; +} diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp deleted file mode 100644 index 1c82447..0000000 --- a/src/rendering/renderer.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include "renderer.h" - -#include "core/log.h" - -#include <glad/glad.h> -#include <GLFW/glfw3.h> -#include <glm/gtc/matrix_transform.hpp> - -namespace Donut -{ - RendererAPI::API RendererAPI::s_api = RendererAPI::API::OpenGL; - - auto RendererAPI::create() -> Scope<RendererAPI> - { - return create_scope<RendererAPI>(); - } - - auto RendererAPI::init() -> void - { - if (!glfwGetCurrentContext()) - { - DONUT_ERROR("No OpenGL context is current! Cannot initialize GLAD."); - return; - } - - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) - { - DONUT_ERROR("Failed to initialize GLAD!"); - return; - } - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LESS); - - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glFrontFace(GL_CCW); - } - - auto RendererAPI::set_viewport(uint32_t x, uint32_t y, uint32_t width, uint32_t height) -> void - { - glViewport(x, y, width, height); - } - - auto RendererAPI::set_clear_color(const glm::vec4& color) -> void - { - glClearColor(color.r, color.g, color.b, color.a); - } - - auto RendererAPI::clear() -> void - { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - } - - auto RendererAPI::enable_depth_test() -> void - { - glEnable(GL_DEPTH_TEST); - } - - auto RendererAPI::disable_depth_test() -> void - { - glDisable(GL_DEPTH_TEST); - } - - auto RendererAPI::set_face_culling(bool enabled) -> void - { - if (enabled) - { - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glFrontFace(GL_CCW); - } - else - glDisable(GL_CULL_FACE); - } - - auto RendererAPI::enable_blending() -> void - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - } - - auto RendererAPI::disable_blending() -> void - { - glDisable(GL_BLEND); - } - - auto RendererAPI::draw_indexed(const Ref<VertexArray>& vertex_array, uint32_t index_count) -> void - { - uint32_t count = index_count ? index_count : vertex_array->get_index_buffer()->get_count(); - glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, nullptr); - glBindTexture(GL_TEXTURE_2D, 0); - } - - auto RendererAPI::draw_arrays(uint32_t vertex_count, uint32_t first) -> void - { - glDrawArrays(GL_TRIANGLES, first, vertex_count); - } - - auto RendererAPI::draw_lines(const Ref<VertexArray>& vertex_array, uint32_t index_count) -> void - { - uint32_t count = index_count ? index_count : vertex_array->get_index_buffer()->get_count(); - glDrawElements(GL_LINES, count, GL_UNSIGNED_INT, nullptr); - } - - auto RendererAPI::bind_texture(uint32_t texture_id, uint32_t slot) -> void - { - glActiveTexture(GL_TEXTURE0 + slot); - glBindTexture(GL_TEXTURE_2D, texture_id); - } - - auto RendererAPI::bind_image_texture(uint32_t texture_id, uint32_t slot, bool read_only) -> void - { - // Image load/store is OpenGL 4.2; the pointer is null on macOS (4.1). - if (glBindImageTexture == nullptr) - return; - glBindImageTexture(slot, texture_id, 0, GL_FALSE, 0, - read_only ? GL_READ_ONLY : GL_WRITE_ONLY, GL_RGBA8); - } - - auto RendererAPI::read_pixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height, - uint32_t format, uint32_t type, void* pixels) -> void - { - glReadPixels(x, y, width, height, format, type, pixels); - } - - auto Renderer::init() -> void - { - RenderCommand::init(); - RenderCommand::enable_depth_test(); - } - - auto Renderer::shutdown() -> void - { - } - - auto Renderer::on_window_resize(uint32_t width, uint32_t height) -> void - { - RenderCommand::set_viewport(0, 0, width, height); - } - - auto Renderer::submit(const Ref<Shader>& shader, - const Ref<VertexArray>& vertex_array, - const glm::mat4& transform, - const glm::mat4& view_projection) -> void - { - shader->bind(); - shader->set_mat4("u_ViewProjection", view_projection); - shader->set_mat4("u_Transform", transform); - - vertex_array->bind(); - RenderCommand::draw_indexed(vertex_array); - } - - Scope<RendererAPI> RenderCommand::s_renderer_api = RendererAPI::create(); - - auto Renderer::set_clear_color(const glm::vec4& color) -> void - { - RenderCommand::set_clear_color(color); - } - - auto Renderer::clear() -> void - { - RenderCommand::clear(); - } -}; diff --git a/src/rendering/renderer.h b/src/rendering/renderer.h deleted file mode 100644 index d7855a4..0000000 --- a/src/rendering/renderer.h +++ /dev/null @@ -1,155 +0,0 @@ -#pragma once - -#include "core/memory.h" -#include "vertex_array.h" -#include "shader.h" -#include "framebuffer.h" - -#include <glm/glm.hpp> - -namespace Donut -{ - // Single concrete backend: the OpenGL renderer. (Vulkan runs through its own - // dedicated VulkanRenderer, not this immediate-mode command layer.) - class RendererAPI - { - public: - enum class API - { - None = 0, - OpenGL = 1, - Vulkan = 2, - }; - - public: - RendererAPI() = default; - ~RendererAPI() = default; - - auto init() -> void; - auto set_viewport(uint32_t x, uint32_t y, uint32_t width, uint32_t height) -> void; - auto set_clear_color(const glm::vec4& color) -> void; - auto clear() -> void; - auto enable_depth_test() -> void; - auto disable_depth_test() -> void; - auto set_face_culling(bool enabled) -> void; - auto enable_blending() -> void; - auto disable_blending() -> void; - - auto draw_indexed(const Ref<VertexArray>& vertex_array, uint32_t index_count = 0) -> void; - auto draw_arrays(uint32_t vertex_count, uint32_t first = 0) -> void; - auto draw_lines(const Ref<VertexArray>& vertex_array, uint32_t index_count = 0) -> void; - auto bind_texture(uint32_t texture_id, uint32_t slot = 0) -> void; - auto bind_image_texture(uint32_t texture_id, uint32_t slot = 0, bool read_only = false) -> void; - auto read_pixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height, - uint32_t format, uint32_t type, void* pixels) -> void; - - inline static auto get_api() -> API { return s_api; } - inline static auto set_api(API api) -> void { s_api = api; } - static auto create() -> Scope<RendererAPI>; - - private: - static API s_api; - }; - - class RenderCommand - { - public: - inline static auto init() -> void - { - s_renderer_api->init(); - } - - inline static auto set_viewport(uint32_t x, uint32_t y, uint32_t width, uint32_t height) -> void - { - s_renderer_api->set_viewport(x, y, width, height); - } - - inline static auto set_clear_color(const glm::vec4& color) -> void - { - s_renderer_api->set_clear_color(color); - } - - inline static auto clear() -> void - { - s_renderer_api->clear(); - } - - inline static auto enable_depth_test() -> void - { - s_renderer_api->enable_depth_test(); - } - - inline static auto disable_depth_test() -> void - { - s_renderer_api->disable_depth_test(); - } - - inline static auto set_face_culling(bool enabled) -> void - { - s_renderer_api->set_face_culling(enabled); - } - - inline static auto enable_blending() -> void - { - s_renderer_api->enable_blending(); - } - - inline static auto disable_blending() -> void - { - s_renderer_api->disable_blending(); - } - - inline static auto draw_indexed(const Ref<VertexArray>& vertex_array, uint32_t index_count = 0) -> void - { - s_renderer_api->draw_indexed(vertex_array, index_count); - } - - inline static auto draw_arrays(uint32_t vertex_count, uint32_t first = 0) -> void - { - s_renderer_api->draw_arrays(vertex_count, first); - } - - inline static auto draw_lines(const Ref<VertexArray>& vertex_array, uint32_t index_count = 0) -> void - { - s_renderer_api->draw_lines(vertex_array, index_count); - } - - inline static auto bind_texture(uint32_t texture_id, uint32_t slot = 0) -> void - { - s_renderer_api->bind_texture(texture_id, slot); - } - - inline static auto bind_image_texture(uint32_t texture_id, uint32_t slot = 0, bool read_only = false) -> void - { - s_renderer_api->bind_image_texture(texture_id, slot, read_only); - } - - inline static auto read_pixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height, - uint32_t format, uint32_t type, void* pixels) -> void - { - s_renderer_api->read_pixels(x, y, width, height, format, type, pixels); - } - - private: - static Scope<RendererAPI> s_renderer_api; - }; - - class Renderer - { - public: - static auto init() -> void; - static auto shutdown() -> void; - - static auto on_window_resize(uint32_t width, uint32_t height) -> void; - - static auto submit(const Ref<Shader>& shader, - const Ref<VertexArray>& vertex_array, - const glm::mat4& transform, - const glm::mat4& view_projection) -> void; - - static auto set_clear_color(const glm::vec4& color) -> void; - static auto clear() -> void; - - inline static auto get_api() -> RendererAPI::API { return RendererAPI::get_api(); } - }; -}; diff --git a/src/rendering/renderer_backend.h b/src/rendering/renderer_backend.h index 6a4f1af..d34983f 100644 --- a/src/rendering/renderer_backend.h +++ b/src/rendering/renderer_backend.h @@ -1,14 +1,15 @@ #pragma once #include <glm/glm.hpp> -#include <functional> -#include <string> -#include <vector> namespace Donut { - // A placeable/editable sphere in the world-builder scene view. Backend- - // agnostic: the app owns the list, every backend renders it the same way. + // Backend-agnostic scene/black-hole data shared by the Application and the + // portable renderers (SceneRenderer, BlackHoleRenderer). The rendering itself + // is written once on the RHI (rendering/rhi.h); these are just the inputs. + + // A placeable/editable sphere in the world-builder scene view. The app owns + // the list; SceneRenderer draws it the same way on every backend. struct SceneObject { glm::vec3 position = { 0.0f, 2.0f, 0.0f }; @@ -28,46 +29,4 @@ namespace Donut int quality_steps = 15000; // geodesic integration steps float fov_degrees = 60.0f; // camera field of view }; - - // The swappable high-level renderer. Each graphics backend (OpenGL, Vulkan, - // and later Metal / D3D12) implements this one interface; the Application, - // states and UI drive it identically, so the app behaves the same no matter - // which backend is active. Backends own their own device/swapchain/pipelines - // and translate these high-level calls into their native API. - class RendererBackend - { - public: - virtual ~RendererBackend() = default; - - // glfwWindow is an opaque GLFW handle (created NO_API for Vulkan/Metal, - // with a GL context for OpenGL). Returns false on failure. - virtual auto init(void* glfwWindow, int width, int height) -> bool = 0; - - // Creates the ImGui context + this backend's ImGui platform/render impl. - virtual auto init_ui() -> bool = 0; - virtual auto shutdown() -> void = 0; - virtual auto resize(int width, int height) -> void = 0; - - // Renders one frame: draws the current view (black hole or scene) from - // the shared state, then invokes build_ui to draw the ImGui panel on top. - virtual auto draw_frame(const glm::vec4& clear_color, - const std::function<void()>& build_ui) -> void = 0; - - // --- shared view / camera state ------------------------------------ - virtual auto set_scene_mode(bool enabled) -> void = 0; // black hole vs scene view - virtual auto is_scene_mode() const -> bool = 0; - virtual auto set_free_fly(bool enabled) -> void = 0; // free-fly vs orbital camera - virtual auto is_free_fly() const -> bool = 0; - virtual auto reset_camera() -> void = 0; - - // --- shared content ------------------------------------------------ - virtual auto set_hdri(const std::string& path) -> void = 0; - virtual auto current_hdri() const -> const std::string& = 0; - virtual auto scene_objects() -> std::vector<SceneObject>& = 0; - virtual auto set_selected_object(int index) -> void = 0; - virtual auto black_hole_params() -> BlackHoleParams& = 0; - - // --- introspection for the UI -------------------------------------- - virtual auto device_name() const -> const std::string& = 0; - }; } diff --git a/src/rendering/rhi/rhi.h b/src/rendering/rhi.h index f64252c..65f10b5 100644 --- a/src/rendering/rhi/rhi.h +++ b/src/rendering/rhi.h @@ -14,7 +14,7 @@ #include <vector> #include <functional> -namespace Donut::rhi +namespace Donut::RHI { enum class Format { @@ -35,9 +35,11 @@ namespace Donut::rhi struct VertexLayout { uint32_t stride = 0; std::vector<VertexAttribute> attributes; }; // A shader resource slot the pipeline exposes. `binding` is the set-0 binding - // index shared by the GLSL/SPIR-V (the app binds resources to these before a draw). + // index used by Vulkan; `name` is the GLSL block/sampler identifier used by the + // OpenGL backend (GL 4.1 has no binding qualifier, so it binds by name). The app + // declares both when building a pipeline (it knows its own shader). enum class ResourceKind { UniformBuffer, Texture }; - struct ResourceSlot { ResourceKind kind; uint32_t binding; }; + struct ResourceSlot { ResourceKind kind; uint32_t binding; std::string name; }; // --- opaque GPU resources (backends subclass) -------------------------- class Buffer { public: virtual ~Buffer() = default; virtual auto update(const void* data, size_t size) -> void = 0; }; @@ -113,13 +115,12 @@ namespace Donut::rhi Filter sample_filter = Filter::Linear, int mip_levels = 1) -> Ref<RenderTarget> = 0; virtual auto create_pipeline(const PipelineDesc& desc) -> Ref<Pipeline> = 0; - // Frame loop: begin_frame returns the swapchain command list (or nullptr if - // the frame is skipped, e.g. minimised); record into it, then end_frame - // submits + presents. record_offscreen runs a self-contained pass whose - // command list targets a RenderTarget (used before the swapchain frame). + // Frame loop: begin_frame returns the frame's command list (or nullptr if + // the frame is skipped, e.g. minimised); record one or more render passes + // into it — off-screen passes into RenderTargets first, then the swapchain + // pass (begin_render_pass(nullptr, ...)) — then end_frame submits + presents. virtual auto begin_frame(const glm::vec4& clear) -> CommandList* = 0; virtual auto end_frame() -> void = 0; - virtual auto record_offscreen(const std::function<void(CommandList&)>& record) -> void = 0; // ImGui lives above the RHI but its platform/render backend is per-device. virtual auto init_imgui() -> void = 0; diff --git a/src/rendering/scene_renderer.cpp b/src/rendering/scene_renderer.cpp new file mode 100644 index 0000000..e276074 --- /dev/null +++ b/src/rendering/scene_renderer.cpp @@ -0,0 +1,217 @@ +#include "scene_renderer.h" + +#include "core/log.h" + +#include <glm/gtc/matrix_transform.hpp> +#include <algorithm> +#include <cmath> +#include <numbers> +#include <vector> + +namespace Donut +{ + using namespace RHI; + + namespace + { + // std140 layouts, matching the Slang ConstantBuffer structs exactly. glm's + // column-major matrices upload directly (the shaders decorate the members + // row_major and use mul(M,v), so the same bytes read correctly on every + // backend, with no transpose). + struct GridUBO { + glm::mat4 view_projection; // 0 + glm::mat4 transform; // 64 + float grid_size; float p0[3]; // 128 + glm::vec3 grid_color; // 144 + float grid_alpha; // 156 + glm::vec3 camera_pos; // 160 + float p1; // 172 + }; + static_assert(sizeof(GridUBO) == 176, "GridUBO std140 layout mismatch"); + + struct SphereUBO { + glm::mat4 view_projection; // 0 + glm::mat4 transform; // 64 + glm::vec3 color; float specular; // 128, 140 + float emission; float p0[3]; // 144 + glm::vec3 light_pos; float p1; // 160, 172 + glm::vec3 camera_pos; int is_selected; // 176, 188 + glm::vec3 outline_color; float outline_width; // 192, 204 + }; + static_assert(sizeof(SphereUBO) == 208, "SphereUBO std140 layout mismatch"); + + struct SkyboxUBO { glm::mat4 projection; glm::mat4 view; }; + static_assert(sizeof(SkyboxUBO) == 128, "SkyboxUBO std140 layout mismatch"); + } + + auto SceneRenderer::init(RHI::Device& device) -> bool + { + m_device = &device; + + // Reference grid: line list on the XZ plane (+/-50, 1-unit cells). Grid.slang + // scales by u_GridSize/50, so u_GridSize = 50 keeps it 1:1. + { + std::vector<glm::vec3> lines; + const int N = 50; + for (int i = -N; i <= N; ++i) + { + lines.push_back({ (float)i, 0.0f, (float)-N }); + lines.push_back({ (float)i, 0.0f, (float) N }); + lines.push_back({ (float)-N, 0.0f, (float)i }); + lines.push_back({ (float) N, 0.0f, (float)i }); + } + m_grid_vertex_count = (int)lines.size(); + m_grid_vb = device.create_buffer(BufferType::Vertex, lines.size() * sizeof(glm::vec3), lines.data()); + m_grid_ubo = device.create_buffer(BufferType::Uniform, sizeof(GridUBO)); + + PipelineDesc d; + d.shader = "grid"; + d.vertex_layout = { sizeof(glm::vec3), { { 0, 3, 0 } } }; + d.resources = { { ResourceKind::UniformBuffer, 0, "GridU" } }; + d.topology = Topology::Lines; + d.blend = BlendMode::AlphaBlend; + d.depth_test = true; d.depth_write = false; d.depth_op = CompareOp::LessEqual; + d.has_depth = true; + m_grid_pipeline = device.create_pipeline(d); + } + + // Lit sphere: unit UV-sphere (pos + normal), placed/scaled per object. + { + std::vector<float> sv; std::vector<uint32_t> si; + const int RINGS = 24, SECTORS = 48; + for (int r = 0; r <= RINGS; ++r) + { + float phi = (float)std::numbers::pi * r / RINGS; + for (int s = 0; s <= SECTORS; ++s) + { + float theta = 2.0f * (float)std::numbers::pi * s / SECTORS; + float x = sinf(phi) * cosf(theta), y = cosf(phi), z = sinf(phi) * sinf(theta); + sv.push_back(x); sv.push_back(y); sv.push_back(z); // position (unit) + sv.push_back(x); sv.push_back(y); sv.push_back(z); // normal == position + } + } + for (int r = 0; r < RINGS; ++r) + for (int s = 0; s < SECTORS; ++s) + { + uint32_t a = r * (SECTORS + 1) + s, b = a + SECTORS + 1; + si.push_back(a); si.push_back(b); si.push_back(a + 1); + si.push_back(b); si.push_back(b + 1); si.push_back(a + 1); + } + m_sphere_index_count = (int)si.size(); + m_sphere_vb = device.create_buffer(BufferType::Vertex, sv.size() * sizeof(float), sv.data()); + m_sphere_ib = device.create_buffer(BufferType::Index, si.size() * sizeof(uint32_t), si.data()); + + PipelineDesc d; + d.shader = "sphere"; + d.vertex_layout = { 6 * sizeof(float), { { 0, 3, 0 }, { 1, 3, 3 * sizeof(float) } } }; + d.resources = { { ResourceKind::UniformBuffer, 0, "SphereU" }, + { ResourceKind::Texture, 1, "u_HDRIEnvironment" } }; + d.topology = Topology::Triangles; + d.depth_test = true; d.depth_write = true; d.depth_op = CompareOp::Less; + d.has_depth = true; + m_sphere_pipeline = device.create_pipeline(d); + } + + // Skybox: a unit cube (36 verts) sampling the HDRI cubemap; the vertex + // shader forces depth 1 (pos.xyww) so it sits behind all scene geometry. + { + const float cube[] = { + -1,-1,-1, 1,-1,-1, 1, 1,-1, 1, 1,-1, -1, 1,-1, -1,-1,-1, + -1,-1, 1, 1,-1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, -1,-1, 1, + -1, 1, 1, -1, 1,-1, -1,-1,-1, -1,-1,-1, -1,-1, 1, -1, 1, 1, + 1, 1, 1, 1, 1,-1, 1,-1,-1, 1,-1,-1, 1,-1, 1, 1, 1, 1, + -1,-1,-1, 1,-1,-1, 1,-1, 1, 1,-1, 1, -1,-1, 1, -1,-1,-1, + -1, 1,-1, 1, 1,-1, 1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1,-1 + }; + m_skybox_vb = device.create_buffer(BufferType::Vertex, sizeof(cube), cube); + m_skybox_ubo = device.create_buffer(BufferType::Uniform, sizeof(SkyboxUBO)); + + PipelineDesc d; + d.shader = "skybox"; + d.vertex_layout = { 3 * sizeof(float), { { 0, 3, 0 } } }; + d.resources = { { ResourceKind::UniformBuffer, 0, "SkyboxU" }, + { ResourceKind::Texture, 1, "u_Skybox" } }; + d.topology = Topology::Triangles; + d.depth_test = false; d.depth_write = false; + d.has_depth = true; + m_skybox_pipeline = device.create_pipeline(d); + } + + DONUT_INFO("SceneRenderer ready ({} grid verts, {} sphere indices, skybox)", + m_grid_vertex_count, m_sphere_index_count); + return true; + } + + auto SceneRenderer::render(RHI::CommandList& cmd, const CameraView& cam, + const std::vector<SceneObject>& objects, int selected, + RHI::Texture* cubemap) -> void + { + // Full-window viewport; flip_y lets the backend match the shared top-left + // orientation (Vulkan flips via a negative-height viewport, GL is a no-op). + cmd.set_viewport(0, 0, cam.fb_width, cam.fb_height, true); + + glm::mat4 vp = cam.projection * cam.view; + + // Skybox background first (depth 1, no depth test/write). + { + SkyboxUBO sky{}; + sky.projection = cam.projection; + sky.view = glm::mat4(glm::mat3(cam.view)); // strip translation + m_skybox_ubo->update(&sky, sizeof(sky)); + cmd.bind_pipeline(m_skybox_pipeline.get()); + cmd.bind_uniform(0, m_skybox_ubo.get()); + cmd.bind_texture(1, cubemap); + cmd.bind_vertex_buffer(m_skybox_vb.get()); + cmd.draw(36); + } + + // Opaque spheres (write depth). One UBO per object slot avoids aliasing the + // per-draw uniforms across the deferred command stream. + { + int count = std::min((int)objects.size(), MAX_OBJECTS); + while ((int)m_sphere_ubos.size() < count) + m_sphere_ubos.push_back(m_device->create_buffer(BufferType::Uniform, sizeof(SphereUBO))); + + cmd.bind_pipeline(m_sphere_pipeline.get()); + for (int i = 0; i < count; ++i) + { + const SceneObject& o = objects[i]; + SphereUBO s{}; + s.view_projection = vp; + s.transform = glm::translate(glm::mat4(1.0f), o.position) + * glm::scale(glm::mat4(1.0f), glm::vec3(o.radius)); + s.color = o.color; + s.specular = 0.6f; + s.emission = 0.0f; + s.light_pos = glm::vec3(10.0f, 20.0f, 10.0f); + s.camera_pos = cam.position; + s.is_selected = (i == selected) ? 1 : 0; + s.outline_color = glm::vec3(1.0f, 1.0f, 0.0f); + s.outline_width = 0.15f; + m_sphere_ubos[i]->update(&s, sizeof(s)); + + cmd.bind_uniform(0, m_sphere_ubos[i].get()); + cmd.bind_texture(1, cubemap); + cmd.bind_vertex_buffer(m_sphere_vb.get()); + cmd.bind_index_buffer(m_sphere_ib.get()); + cmd.draw_indexed(m_sphere_index_count); + } + } + + // Transparent grid on top (tests depth, doesn't write). + { + GridUBO g{}; + g.view_projection = vp; + g.transform = glm::mat4(1.0f); + g.grid_size = 50.0f; + g.grid_color = glm::vec3(0.55f, 0.55f, 0.6f); + g.grid_alpha = 0.75f; + g.camera_pos = cam.position; + m_grid_ubo->update(&g, sizeof(g)); + cmd.bind_pipeline(m_grid_pipeline.get()); + cmd.bind_uniform(0, m_grid_ubo.get()); + cmd.bind_vertex_buffer(m_grid_vb.get()); + cmd.draw(m_grid_vertex_count); + } + } +} diff --git a/src/rendering/scene_renderer.h b/src/rendering/scene_renderer.h new file mode 100644 index 0000000..eafd56e --- /dev/null +++ b/src/rendering/scene_renderer.h @@ -0,0 +1,56 @@ +#pragma once + +#include "rhi.h" +#include "renderer_backend.h" // SceneObject +#include <glm/glm.hpp> +#include <vector> + +namespace Donut +{ + // The world-builder scene view (skybox + lit spheres + reference grid), + // written ONCE against the RHI so it renders identically on every backend. + // The Application owns the camera, the object list and the HDRI cubemap and + // passes them in each frame; this class owns only the GPU resources (meshes, + // pipelines, per-object uniform buffers) and records the draws. + struct CameraView + { + glm::mat4 view; + glm::mat4 projection; + glm::vec3 position; + int fb_width = 1; + int fb_height = 1; + }; + + class SceneRenderer + { + public: + // Builds the grid/sphere/skybox meshes and pipelines on the given device. + auto init(RHI::Device& device) -> bool; + + // Records the scene into the currently-open swapchain render pass. `cubemap` + // is the shared HDRI environment (sphere ambient + skybox background). + auto render(RHI::CommandList& cmd, const CameraView& cam, + const std::vector<SceneObject>& objects, int selected, + RHI::Texture* cubemap) -> void; + + private: + static constexpr int MAX_OBJECTS = 64; + + RHI::Device* m_device = nullptr; + + Ref<RHI::Pipeline> m_grid_pipeline; + Ref<RHI::Buffer> m_grid_vb; + Ref<RHI::Buffer> m_grid_ubo; + int m_grid_vertex_count = 0; + + Ref<RHI::Pipeline> m_sphere_pipeline; + Ref<RHI::Buffer> m_sphere_vb; + Ref<RHI::Buffer> m_sphere_ib; + int m_sphere_index_count = 0; + std::vector<Ref<RHI::Buffer>> m_sphere_ubos; // one per object slot + + Ref<RHI::Pipeline> m_skybox_pipeline; + Ref<RHI::Buffer> m_skybox_vb; + Ref<RHI::Buffer> m_skybox_ubo; + }; +} diff --git a/src/rendering/texture_manager.cpp b/src/rendering/texture_manager.cpp deleted file mode 100644 index 3afba0d..0000000 --- a/src/rendering/texture_manager.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "texture_manager.h" -#include "renderer.h" - -#include <glad/glad.h> - -namespace Donut -{ - auto TextureManager::create_texture(uint32_t width, uint32_t height) -> Ref<Texture2D> - { - return create_ref<Texture2D>(width, height); - } - - auto TextureManager::bind_texture(uint32_t texture_id, uint32_t slot) -> void - { - RenderCommand::bind_texture(texture_id, slot); - } - - auto TextureManager::bind_image_texture(uint32_t texture_id, uint32_t slot, bool read_only) -> void - { - RenderCommand::bind_image_texture(texture_id, slot, read_only); - } - - auto TextureManager::set_texture_data(uint32_t texture_id, void* data, uint32_t width, uint32_t height) -> void - { - glBindTexture(GL_TEXTURE_2D, texture_id); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); - } - - auto TextureManager::resize_texture(uint32_t texture_id, uint32_t width, uint32_t height) -> void - { - glBindTexture(GL_TEXTURE_2D, texture_id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - } -}; diff --git a/src/rendering/texture_manager.h b/src/rendering/texture_manager.h deleted file mode 100644 index 02c32b1..0000000 --- a/src/rendering/texture_manager.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "core/memory.h" -#include "rendering/texture.h" -#include <cstdint> - -namespace Donut -{ - class TextureManager - { - public: - static auto create_texture(uint32_t width, uint32_t height) -> Ref<Texture2D>; - - static auto bind_texture(uint32_t texture_id, uint32_t slot = 0) -> void; - static auto bind_image_texture(uint32_t texture_id, uint32_t slot = 0, bool read_only = false) -> void; - static auto set_texture_data(uint32_t texture_id, void* data, uint32_t width, uint32_t height) -> void; - static auto resize_texture(uint32_t texture_id, uint32_t width, uint32_t height) -> void; - }; -}; diff --git a/src/rendering/uniform_buffer.cpp b/src/rendering/uniform_buffer.cpp deleted file mode 100644 index c4118b8..0000000 --- a/src/rendering/uniform_buffer.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "uniform_buffer.h" - -#include <glad/glad.h> - -namespace Donut -{ - UniformBuffer::UniformBuffer(uint32_t size, uint32_t binding) - : m_size(size), m_binding(binding) - { - glGenBuffers(1, &m_renderer_id); - glBindBuffer(GL_UNIFORM_BUFFER, m_renderer_id); - glBufferData(GL_UNIFORM_BUFFER, size, nullptr, GL_DYNAMIC_DRAW); - glBindBufferBase(GL_UNIFORM_BUFFER, binding, m_renderer_id); - } - - UniformBuffer::~UniformBuffer() - { - glDeleteBuffers(1, &m_renderer_id); - } - - auto UniformBuffer::set_data(const void* data, uint32_t size, uint32_t offset) -> void - { - glBindBuffer(GL_UNIFORM_BUFFER, m_renderer_id); - glBufferSubData(GL_UNIFORM_BUFFER, offset, size, data); - } - - auto UniformBuffer::bind(uint32_t binding) -> void - { - glBindBufferBase(GL_UNIFORM_BUFFER, binding, m_renderer_id); - } - - auto UniformBuffer::create(uint32_t size, uint32_t binding) -> Ref<UniformBuffer> - { - return create_ref<UniformBuffer>(size, binding); - } -}; diff --git a/src/rendering/uniform_buffer.h b/src/rendering/uniform_buffer.h deleted file mode 100644 index b5617b9..0000000 --- a/src/rendering/uniform_buffer.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "core/memory.h" -#include <cstdint> - -namespace Donut -{ - class UniformBuffer - { - public: - UniformBuffer(uint32_t size, uint32_t binding); - ~UniformBuffer(); - - auto set_data(const void* data, uint32_t size, uint32_t offset = 0) -> void; - auto bind(uint32_t binding) -> void; - - static auto create(uint32_t size, uint32_t binding) -> Ref<UniformBuffer>; - - private: - uint32_t m_renderer_id = 0; - uint32_t m_size = 0; - uint32_t m_binding = 0; - }; -}; diff --git a/src/rendering/vertex_array.cpp b/src/rendering/vertex_array.cpp deleted file mode 100644 index cddcf35..0000000 --- a/src/rendering/vertex_array.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include <glad/glad.h> - -#include "vertex_array.h" - -namespace Donut -{ - VertexArray::VertexArray() - { - // glCreateVertexArrays is 4.5 DSA; macOS caps at 4.1. glGenVertexArrays - // reserves the name and the VAO is created on first bind (done below). - glGenVertexArrays(1, &m_renderer_id); - } - - VertexArray::~VertexArray() - { - glDeleteVertexArrays(1, &m_renderer_id); - } - - auto VertexArray::bind() const -> void - { - glBindVertexArray(m_renderer_id); - } - - auto VertexArray::unbind() const -> void - { - glBindVertexArray(0); - } - - auto VertexArray::add_vertex_buffer(const Ref<VertexBuffer>& vertex_buffer) -> void - { - glBindVertexArray(m_renderer_id); - vertex_buffer->bind(); - - const auto& layout = vertex_buffer->get_layout(); - for (const auto& element : layout.get_elements()) - { - glEnableVertexAttribArray(m_vertex_buffer_index); - glVertexAttribPointer(m_vertex_buffer_index, - element.count, - element.type, - element.normalized ? GL_TRUE : GL_FALSE, - layout.get_stride(), - reinterpret_cast<const void*>(static_cast<uintptr_t>(element.offset))); - m_vertex_buffer_index++; - } - - m_vertex_buffers.push_back(vertex_buffer); - } - - auto VertexArray::set_index_buffer(const Ref<IndexBuffer>& index_buffer) -> void - { - glBindVertexArray(m_renderer_id); - index_buffer->bind(); - m_index_buffer = index_buffer; - } - - auto VertexArray::create() -> VertexArray* - { - return new VertexArray(); - } -}; diff --git a/src/rendering/vertex_array.h b/src/rendering/vertex_array.h deleted file mode 100644 index 1f06758..0000000 --- a/src/rendering/vertex_array.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "core/memory.h" - -#include "vertex_buffer.h" -#include "index_buffer.h" - -#include <vector> - -namespace Donut -{ - class VertexArray - { - public: - VertexArray(); - ~VertexArray(); - - auto bind() const -> void; - auto unbind() const -> void; - - auto add_vertex_buffer(const Ref<VertexBuffer>& vertex_buffer) -> void; - auto set_index_buffer(const Ref<IndexBuffer>& index_buffer) -> void; - - auto get_vertex_buffers() const -> const std::vector<Ref<VertexBuffer>>& { return m_vertex_buffers; } - auto get_index_buffer() const -> const Ref<IndexBuffer>& { return m_index_buffer; } - - static auto create() -> VertexArray*; - - private: - uint32_t m_renderer_id = 0; - uint32_t m_vertex_buffer_index = 0; - std::vector<Ref<VertexBuffer>> m_vertex_buffers; - Ref<IndexBuffer> m_index_buffer; - }; -}; diff --git a/src/rendering/vertex_buffer.cpp b/src/rendering/vertex_buffer.cpp deleted file mode 100644 index 1bc471c..0000000 --- a/src/rendering/vertex_buffer.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "vertex_buffer.h" - -#include <glad/glad.h> - -namespace Donut -{ - auto VertexBufferElement::get_size_of_type(uint32_t type) -> uint32_t - { - switch (type) - { - case 0x1406: return 4; // GL_FLOAT - case 0x1405: return 4; // GL_UNSIGNED_INT - case 0x1401: return 1; // GL_UNSIGNED_BYTE - default: return 0; - } - } - - VertexBuffer::VertexBuffer(const void* data, uint32_t size) - { - glGenBuffers(1, &m_renderer_id); // glCreateBuffers is 4.5 DSA; unavailable on macOS 4.1 - glBindBuffer(GL_ARRAY_BUFFER, m_renderer_id); - glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW); - } - - VertexBuffer::~VertexBuffer() - { - glDeleteBuffers(1, &m_renderer_id); - } - - auto VertexBuffer::bind() const -> void - { - glBindBuffer(GL_ARRAY_BUFFER, m_renderer_id); - } - - auto VertexBuffer::unbind() const -> void - { - glBindBuffer(GL_ARRAY_BUFFER, 0); - } - - auto VertexBuffer::set_data(const void* data, uint32_t size) -> void - { - glBindBuffer(GL_ARRAY_BUFFER, m_renderer_id); - glBufferSubData(GL_ARRAY_BUFFER, 0, size, data); - } - - auto VertexBuffer::create(const void* data, uint32_t size) -> VertexBuffer* - { - return new VertexBuffer(data, size); - } -}; diff --git a/src/rendering/vertex_buffer.h b/src/rendering/vertex_buffer.h deleted file mode 100644 index 4e61700..0000000 --- a/src/rendering/vertex_buffer.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include <cstdint> -#include <vector> - -namespace Donut -{ - struct VertexBufferElement - { - uint32_t type; - uint32_t count; - uint8_t normalized; - uint32_t offset; - - static auto get_size_of_type(uint32_t type) -> uint32_t; - }; - - class VertexBufferLayout - { - public: - VertexBufferLayout() = default; - ~VertexBufferLayout() = default; - - template<typename T> - auto push(uint32_t count) -> void - { - static_assert(false); - } - - template<> - void push<float>(uint32_t count) - { - m_elements.push_back({ 0x1406, count, 0, m_stride }); // GL_FLOAT - m_stride += count * VertexBufferElement::get_size_of_type(0x1406); - } - - template<> - void push<uint32_t>(uint32_t count) - { - m_elements.push_back({ 0x1405, count, 0, m_stride }); // GL_UNSIGNED_INT - m_stride += count * VertexBufferElement::get_size_of_type(0x1405); - } - - template<> - void push<uint8_t>(uint32_t count) - { - m_elements.push_back({ 0x1401, count, 1, m_stride }); // GL_UNSIGNED_BYTE - m_stride += count * VertexBufferElement::get_size_of_type(0x1401); - } - - inline auto get_elements() const -> const std::vector<VertexBufferElement>& { return m_elements; } - inline auto get_stride() const -> uint32_t { return m_stride; } - - private: - std::vector<VertexBufferElement> m_elements; - uint32_t m_stride = 0; - }; - - class VertexBuffer - { - public: - VertexBuffer(const void* data, uint32_t size); - ~VertexBuffer(); - - auto bind() const -> void; - auto unbind() const -> void; - auto set_data(const void* data, uint32_t size) -> void; - - auto get_layout() const -> const VertexBufferLayout& { return m_layout; } - auto set_layout(const VertexBufferLayout& layout) -> void { m_layout = layout; } - - static auto create(const void* data, uint32_t size) -> VertexBuffer*; - - private: - uint32_t m_renderer_id = 0; - VertexBufferLayout m_layout; - }; -}; |
