Skip to content

Commit 166c910

Browse files
authored
Merge pull request #46 from devscafecommunity/12-rendering-hardware-interface-rhi
12 rendering hardware interface rhi
2 parents 5275a05 + fc2a10c commit 166c910

7 files changed

Lines changed: 1091 additions & 2 deletions

File tree

CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
1212
add_compile_definitions(CF_DEBUG)
1313
endif()
1414

15+
# ── SDL3 (optional – RHI is only built when SDL3 is available) ──
16+
find_package(SDL3 CONFIG QUIET)
17+
1518
add_library(Caffeine
1619
src/core/Timer.cpp
1720
src/core/GameLoop.cpp
@@ -20,11 +23,26 @@ add_library(Caffeine
2023
src/debug/Profiler.cpp
2124
)
2225

26+
if(SDL3_FOUND)
27+
target_sources(Caffeine PRIVATE
28+
src/rhi/RenderDevice.cpp
29+
src/rhi/CommandBuffer.cpp
30+
)
31+
endif()
32+
2333
target_include_directories(Caffeine PUBLIC
2434
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
2535
$<INSTALL_INTERFACE:include>
2636
)
2737

38+
if(SDL3_FOUND)
39+
target_link_libraries(Caffeine PUBLIC SDL3::SDL3)
40+
target_compile_definitions(Caffeine PUBLIC CF_HAS_SDL3=1)
41+
message(STATUS "SDL3 found – RHI module enabled")
42+
else()
43+
message(STATUS "SDL3 not found – RHI module disabled")
44+
endif()
45+
2846
target_compile_features(Caffeine PUBLIC cxx_std_20)
2947

3048
add_library(Caffeine::Core ALIAS Caffeine)

src/rhi/CommandBuffer.cpp

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// ============================================================================
2+
// @file CommandBuffer.cpp
3+
// @brief CommandBuffer implementation
4+
// ============================================================================
5+
#include "CommandBuffer.hpp"
6+
7+
#include <SDL3/SDL_gpu.h>
8+
9+
namespace Caffeine::RHI {
10+
11+
CommandBuffer::CommandBuffer(CommandBuffer&& other) noexcept
12+
: m_device(other.m_device)
13+
, m_cmdBuffer(other.m_cmdBuffer)
14+
, m_renderPass(other.m_renderPass)
15+
, m_inRenderPass(other.m_inRenderPass)
16+
, m_acquired(other.m_acquired) {
17+
other.m_device = nullptr;
18+
other.m_cmdBuffer = nullptr;
19+
other.m_renderPass = nullptr;
20+
other.m_inRenderPass = false;
21+
other.m_acquired = false;
22+
}
23+
24+
CommandBuffer& CommandBuffer::operator=(CommandBuffer&& other) noexcept {
25+
if (this != &other) {
26+
m_device = other.m_device;
27+
m_cmdBuffer = other.m_cmdBuffer;
28+
m_renderPass = other.m_renderPass;
29+
m_inRenderPass = other.m_inRenderPass;
30+
m_acquired = other.m_acquired;
31+
other.m_device = nullptr;
32+
other.m_cmdBuffer = nullptr;
33+
other.m_renderPass = nullptr;
34+
other.m_inRenderPass = false;
35+
other.m_acquired = false;
36+
}
37+
return *this;
38+
}
39+
40+
void CommandBuffer::acquire(SDL_GPUDevice* device) {
41+
m_device = device;
42+
m_cmdBuffer = SDL_AcquireGPUCommandBuffer(device);
43+
m_acquired = (m_cmdBuffer != nullptr);
44+
}
45+
46+
void CommandBuffer::submit() {
47+
if (m_cmdBuffer) {
48+
SDL_SubmitGPUCommandBuffer(m_cmdBuffer);
49+
m_cmdBuffer = nullptr;
50+
}
51+
m_acquired = false;
52+
}
53+
54+
void CommandBuffer::reset() {
55+
m_cmdBuffer = nullptr;
56+
m_renderPass = nullptr;
57+
m_inRenderPass = false;
58+
m_acquired = false;
59+
}
60+
61+
void CommandBuffer::beginRenderPass(const RenderPassDesc& desc) {
62+
if (!m_cmdBuffer || m_inRenderPass) {
63+
return;
64+
}
65+
66+
// This begins a render pass targeting the swapchain texture.
67+
// For off-screen rendering, the caller would provide a texture target.
68+
// For now, this sets up the clear color but actual render target
69+
// binding happens in RenderDevice::endFrame with the swapchain texture.
70+
m_inRenderPass = true;
71+
(void)desc;
72+
}
73+
74+
void CommandBuffer::endRenderPass() {
75+
if (!m_inRenderPass) {
76+
return;
77+
}
78+
79+
if (m_renderPass) {
80+
SDL_EndGPURenderPass(m_renderPass);
81+
m_renderPass = nullptr;
82+
}
83+
84+
m_inRenderPass = false;
85+
}
86+
87+
void CommandBuffer::bindPipeline(Pipeline* pipeline) {
88+
if (!m_renderPass || !pipeline || !pipeline->handle) {
89+
return;
90+
}
91+
SDL_BindGPUGraphicsPipeline(m_renderPass, pipeline->handle);
92+
}
93+
94+
void CommandBuffer::bindVertexBuffer(Buffer* buf, u32 slot) {
95+
if (!m_renderPass || !buf || !buf->handle) {
96+
return;
97+
}
98+
SDL_GPUBufferBinding binding{};
99+
binding.buffer = buf->handle;
100+
binding.offset = 0;
101+
SDL_BindGPUVertexBuffers(m_renderPass, slot, &binding, 1);
102+
}
103+
104+
void CommandBuffer::bindIndexBuffer(Buffer* buf) {
105+
if (!m_renderPass || !buf || !buf->handle) {
106+
return;
107+
}
108+
SDL_GPUBufferBinding binding{};
109+
binding.buffer = buf->handle;
110+
binding.offset = 0;
111+
SDL_BindGPUIndexBuffer(m_renderPass, &binding, SDL_GPU_INDEXELEMENTSIZE_32BIT);
112+
}
113+
114+
void CommandBuffer::bindTexture(Texture* tex, u32 slot) {
115+
if (!m_renderPass || !tex || !tex->handle) {
116+
return;
117+
}
118+
SDL_GPUTextureSamplerBinding samplerBinding{};
119+
samplerBinding.texture = tex->handle;
120+
samplerBinding.sampler = nullptr;
121+
SDL_BindGPUFragmentSamplers(m_renderPass, slot, &samplerBinding, 1);
122+
}
123+
124+
void CommandBuffer::setViewport(f32 x, f32 y, f32 w, f32 h) {
125+
if (!m_renderPass) {
126+
return;
127+
}
128+
SDL_GPUViewport viewport{};
129+
viewport.x = x;
130+
viewport.y = y;
131+
viewport.w = w;
132+
viewport.h = h;
133+
viewport.min_depth = 0.0f;
134+
viewport.max_depth = 1.0f;
135+
SDL_SetGPUViewport(m_renderPass, &viewport);
136+
}
137+
138+
void CommandBuffer::setScissor(u32 x, u32 y, u32 w, u32 h) {
139+
if (!m_renderPass) {
140+
return;
141+
}
142+
SDL_Rect scissor{};
143+
scissor.x = static_cast<int>(x);
144+
scissor.y = static_cast<int>(y);
145+
scissor.w = static_cast<int>(w);
146+
scissor.h = static_cast<int>(h);
147+
SDL_SetGPUScissor(m_renderPass, &scissor);
148+
}
149+
150+
void CommandBuffer::draw(u32 vertexCount, u32 firstVertex) {
151+
if (!m_renderPass) {
152+
return;
153+
}
154+
SDL_DrawGPUPrimitives(m_renderPass, vertexCount, 1, firstVertex, 0);
155+
}
156+
157+
void CommandBuffer::drawIndexed(u32 indexCount, u32 firstIndex, i32 vertexOffset) {
158+
if (!m_renderPass) {
159+
return;
160+
}
161+
SDL_DrawGPUIndexedPrimitives(m_renderPass, indexCount, 1, firstIndex, vertexOffset, 0);
162+
}
163+
164+
void CommandBuffer::drawInstanced(u32 vertexCount, u32 instanceCount,
165+
u32 firstVertex, u32 firstInstance) {
166+
if (!m_renderPass) {
167+
return;
168+
}
169+
SDL_DrawGPUPrimitives(m_renderPass, vertexCount, instanceCount,
170+
firstVertex, firstInstance);
171+
}
172+
173+
void CommandBuffer::pushUniformData(ShaderStage stage, u32 slot,
174+
const void* data, u32 size) {
175+
if (!m_cmdBuffer || !data || size == 0) {
176+
return;
177+
}
178+
SDL_PushGPUVertexUniformData(m_cmdBuffer, slot, data, size);
179+
if (stage == ShaderStage::Fragment) {
180+
SDL_PushGPUFragmentUniformData(m_cmdBuffer, slot, data, size);
181+
}
182+
}
183+
184+
} // namespace Caffeine::RHI

src/rhi/CommandBuffer.hpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// ============================================================================
2+
// @file CommandBuffer.hpp
3+
// @brief GPU command buffer — encapsulates per-frame GPU commands
4+
// @note Part of rhi/ module
5+
// ============================================================================
6+
#pragma once
7+
8+
#include "RenderDevice.hpp"
9+
10+
namespace Caffeine::RHI {
11+
12+
// ============================================================================
13+
// @brief Command buffer — records GPU commands for a single frame.
14+
//
15+
// Lifecycle:
16+
// 1. Obtained from RenderDevice::beginFrame()
17+
// 2. beginRenderPass() / endRenderPass() — bracket a render pass
18+
// 3. Bind resources and issue draw calls between begin/end
19+
// 4. Returned to RenderDevice::endFrame() for submission
20+
// ============================================================================
21+
class CommandBuffer {
22+
public:
23+
CommandBuffer() = default;
24+
~CommandBuffer() = default;
25+
26+
// Non-copyable
27+
CommandBuffer(const CommandBuffer&) = delete;
28+
CommandBuffer& operator=(const CommandBuffer&) = delete;
29+
30+
// Movable
31+
CommandBuffer(CommandBuffer&& other) noexcept;
32+
CommandBuffer& operator=(CommandBuffer&& other) noexcept;
33+
34+
void beginRenderPass(const RenderPassDesc& desc);
35+
void endRenderPass();
36+
37+
void bindPipeline(Pipeline* pipeline);
38+
void bindVertexBuffer(Buffer* buf, u32 slot = 0);
39+
void bindIndexBuffer(Buffer* buf);
40+
void bindTexture(Texture* tex, u32 slot = 0);
41+
void setViewport(f32 x, f32 y, f32 w, f32 h);
42+
void setScissor(u32 x, u32 y, u32 w, u32 h);
43+
44+
void draw(u32 vertexCount, u32 firstVertex = 0);
45+
void drawIndexed(u32 indexCount, u32 firstIndex = 0, i32 vertexOffset = 0);
46+
void drawInstanced(u32 vertexCount, u32 instanceCount,
47+
u32 firstVertex = 0, u32 firstInstance = 0);
48+
49+
void pushUniformData(ShaderStage stage, u32 slot, const void* data, u32 size);
50+
51+
bool isInRenderPass() const { return m_inRenderPass; }
52+
53+
SDL_GPUCommandBuffer* nativeHandle() const { return m_cmdBuffer; }
54+
SDL_GPURenderPass* nativeRenderPass() const { return m_renderPass; }
55+
56+
private:
57+
friend class RenderDevice;
58+
59+
void acquire(SDL_GPUDevice* device);
60+
void submit();
61+
void reset();
62+
63+
SDL_GPUDevice* m_device = nullptr;
64+
SDL_GPUCommandBuffer* m_cmdBuffer = nullptr;
65+
SDL_GPURenderPass* m_renderPass = nullptr;
66+
bool m_inRenderPass = false;
67+
bool m_acquired = false;
68+
};
69+
70+
} // namespace Caffeine::RHI

0 commit comments

Comments
 (0)