#pragma once // Donut RHI (Render Hardware Interface): a small, portable GPU abstraction that // OpenGL, Vulkan (and later Metal / D3D12) implement behind ONE interface, so the // app's rendering — the black hole, the scene — is written ONCE on top and runs // on any backend. The shape is modelled on the explicit APIs (baked pipelines, // recorded command lists, explicit render targets); OpenGL emulates that, which // is easy, whereas the reverse (making Vulkan speak GL's immediate mode) is not. #include "core/memory.h" #include #include #include #include #include namespace Donut::RHI { enum class Format { RGBA8, // 8-bit unorm colour (swapchain / LDR targets) RGBA16F, // half-float colour (HDR / cubemap) D32, // 32-bit depth }; enum class BufferType { Vertex, Index, Uniform }; enum class Topology { Triangles, Lines }; enum class CullMode { None, Back, Front }; enum class BlendMode { Opaque, AlphaBlend }; enum class CompareOp { Always, Less, LessEqual }; enum class Filter { Nearest, Linear }; // One vertex attribute; offsets/stride are in bytes. `components` is 1..4 floats. struct VertexAttribute { uint32_t location; uint32_t components; uint32_t offset; }; struct VertexLayout { uint32_t stride = 0; std::vector attributes; }; // A shader resource slot the pipeline exposes. `binding` is the set-0 binding // 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; 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; }; class Texture { public: virtual ~Texture() = default; }; class Pipeline{ public: virtual ~Pipeline() = default; }; // An off-screen target (colour, optional depth). The swapchain is the implicit // default target, addressed by passing nullptr to begin_render_pass. class RenderTarget { public: virtual ~RenderTarget() = default; virtual auto width() const -> int = 0; virtual auto height() const -> int = 0; virtual auto color_texture() -> Texture* = 0; // to sample this target's colour }; struct PipelineDesc { std::string shader; // base name; backend loads .spv or .glsl VertexLayout vertex_layout; std::vector resources; // UBO/texture slots the shader reads Topology topology = Topology::Triangles; CullMode cull = CullMode::None; BlendMode blend = BlendMode::Opaque; bool depth_test = false; bool depth_write = false; CompareOp depth_op = CompareOp::Less; Format color_format = Format::RGBA8; // format of the target it renders into bool has_depth = false; // target has a depth attachment }; // Records draws for one frame. Obtained from Device::begin_frame (targets the // swapchain) or created transiently for off-screen passes via the Device. class CommandList { public: virtual ~CommandList() = default; // target == nullptr renders to the swapchain; otherwise to the RenderTarget. virtual auto begin_render_pass(RenderTarget* target, const glm::vec4& clear) -> void = 0; virtual auto end_render_pass() -> void = 0; virtual auto bind_pipeline(Pipeline* pipeline) -> void = 0; virtual auto set_viewport(int x, int y, int w, int h, bool flip_y = false) -> void = 0; virtual auto bind_uniform(uint32_t binding, Buffer* ubo) -> void = 0; virtual auto bind_texture(uint32_t binding, Texture* texture) -> void = 0; virtual auto bind_vertex_buffer(Buffer* vb) -> void = 0; virtual auto bind_index_buffer(Buffer* ib) -> void = 0; // 32-bit indices virtual auto draw(uint32_t vertex_count) -> void = 0; virtual auto draw_indexed(uint32_t index_count) -> void = 0; }; // The backend root: owns the device/swapchain and creates every resource. class Device { public: virtual ~Device() = default; // glfwWindow: opaque GLFW handle (GL context for OpenGL, NO_API otherwise). virtual auto init(void* glfwWindow, int width, int height) -> bool = 0; virtual auto shutdown() -> void = 0; virtual auto resize(int width, int height) -> void = 0; virtual auto wait_idle() -> void = 0; virtual auto create_buffer(BufferType type, size_t size, const void* data = nullptr) -> Ref = 0; virtual auto create_texture(int width, int height, Format format, Filter filter, const void* data = nullptr) -> Ref = 0; virtual auto create_cubemap_from_hdri(const std::string& equirect_path) -> Ref = 0; virtual auto create_render_target(int width, int height, Format color, bool with_depth, Filter sample_filter = Filter::Linear, int mip_levels = 1) -> Ref = 0; virtual auto create_pipeline(const PipelineDesc& desc) -> Ref = 0; // 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; // ImGui lives above the RHI but its platform/render backend is per-device. virtual auto init_imgui() -> void = 0; virtual auto imgui_new_frame() -> void = 0; virtual auto imgui_render(CommandList& swapchain_cmds) -> void = 0; virtual auto device_name() const -> const std::string& = 0; }; }