Skip to content

Commit 9df99bd

Browse files
committed
Support multiple color attachments per framebuffer.
1 parent fa21e4b commit 9df99bd

5 files changed

Lines changed: 101 additions & 59 deletions

File tree

Examples/Hello.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ static void initScene(AppState *app) {
170170
pipelineInfo.multisample_state.sample_count = app->framebuffer->sampleCount;
171171
pipelineInfo.target_info = (SDL_GPUGraphicsPipelineTargetInfo) {
172172
.color_target_descriptions = &(SDL_GPUColorTargetDescription) {
173-
.format = app->framebuffer->colorTexture->format,
173+
.format = app->framebuffer->colorTextures[0]->format,
174174
.blend_state = GPU_BlendStateOpaque,
175175
},
176176
.num_color_targets = 1,
@@ -207,7 +207,7 @@ static void drawScene(AppState *app, CommandBuffer *commands) {
207207
$(commands, pushVertexUniformData, 0, modelViewProjection.f, sizeof(modelViewProjection));
208208

209209
const SDL_FColor clearColor = { 0.1f, 0.1f, 0.2f, 1.f };
210-
const SDL_GPUColorTargetInfo color = $(app->framebuffer, colorTargetInfo, SDL_GPU_LOADOP_CLEAR, SDL_GPU_STOREOP_STORE, &clearColor);
210+
const SDL_GPUColorTargetInfo color = $(app->framebuffer, colorTargetInfo, 0, SDL_GPU_LOADOP_CLEAR, SDL_GPU_STOREOP_STORE, &clearColor);
211211
const SDL_GPUDepthStencilTargetInfo depth = $(app->framebuffer, depthTargetInfo, SDL_GPU_LOADOP_CLEAR, SDL_GPU_STOREOP_DONT_CARE, 1.f);
212212

213213
RenderPass *pass = $(commands, beginRenderPass, &color, 1, &depth);
@@ -243,7 +243,8 @@ SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[]) {
243243
const SDL_GPUTextureFormat colorFormat = $(app->renderDevice, getSwapchainTextureFormat);
244244
app->framebuffer = $(app->renderDevice, createFramebuffer, &(GPU_FramebufferCreateInfo) {
245245
.size = MakeSize(w, h),
246-
.colorFormat = colorFormat,
246+
.colorFormats = { colorFormat },
247+
.numColorTargets = 1,
247248
.depthFormat = SDL_GPU_TEXTUREFORMAT_D16_UNORM,
248249
.sampleCount = HELLO_MSAA,
249250
});

Examples/HelloCompute.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ static void initParticles(AppState *app) {
135135
pipelineInfo.multisample_state.sample_count = app->framebuffer->sampleCount;
136136
pipelineInfo.target_info = (SDL_GPUGraphicsPipelineTargetInfo) {
137137
.color_target_descriptions = &(SDL_GPUColorTargetDescription) {
138-
.format = app->framebuffer->colorTexture->format,
138+
.format = app->framebuffer->colorTextures[0]->format,
139139
.blend_state = GPU_BlendStateOpaque,
140140
},
141141
.num_color_targets = 1,
@@ -162,7 +162,7 @@ static void drawParticles(AppState *app, CommandBuffer *commands) {
162162
release(computePass);
163163

164164
const SDL_FColor clearColor = { 0.05f, 0.05f, 0.1f, 1.f };
165-
const SDL_GPUColorTargetInfo color = $(app->framebuffer, colorTargetInfo, SDL_GPU_LOADOP_CLEAR, SDL_GPU_STOREOP_STORE, &clearColor);
165+
const SDL_GPUColorTargetInfo color = $(app->framebuffer, colorTargetInfo, 0, SDL_GPU_LOADOP_CLEAR, SDL_GPU_STOREOP_STORE, &clearColor);
166166

167167
RenderPass *pass = $(commands, beginRenderPass, &color, 1, NULL);
168168
$(pass, bindPipeline, app->graphicsPipeline);
@@ -197,7 +197,8 @@ SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[]) {
197197
const SDL_GPUTextureFormat colorFormat = $(app->renderDevice, getSwapchainTextureFormat);
198198
app->framebuffer = $(app->renderDevice, createFramebuffer, &(GPU_FramebufferCreateInfo) {
199199
.size = MakeSize(w, h),
200-
.colorFormat = colorFormat,
200+
.colorFormats = { colorFormat },
201+
.numColorTargets = 1,
201202
.sampleCount = HELLO_MSAA,
202203
});
203204

Sources/ObjectivelyGPU/Framebuffer.c

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ static void dealloc(Object *self) {
3838

3939
Framebuffer *this = (Framebuffer *) self;
4040

41-
release(this->colorTexture);
42-
release(this->resolveTexture);
41+
for (Uint32 i = 0; i < this->numColorTargets; i++) {
42+
release(this->colorTextures[i]);
43+
release(this->resolveTextures[i]);
44+
}
4345
release(this->depthTexture);
4446

4547
release(this->device);
@@ -50,27 +52,28 @@ static void dealloc(Object *self) {
5052
#pragma mark - Framebuffer
5153

5254
/**
53-
* @fn SDL_GPUColorTargetInfo Framebuffer::colorTargetInfo(const Framebuffer *self, SDL_GPULoadOp loadOp, SDL_GPUStoreOp storeOp, const SDL_FColor *clearColor)
55+
* @fn SDL_GPUColorTargetInfo Framebuffer::colorTargetInfo(const Framebuffer *self, Uint32 index, SDL_GPULoadOp loadOp, SDL_GPUStoreOp storeOp, const SDL_FColor *clearColor)
5456
* @memberof Framebuffer
5557
*/
56-
static SDL_GPUColorTargetInfo colorTargetInfo(const Framebuffer *self, SDL_GPULoadOp loadOp, SDL_GPUStoreOp storeOp, const SDL_FColor *clearColor) {
58+
static SDL_GPUColorTargetInfo colorTargetInfo(const Framebuffer *self, Uint32 index, SDL_GPULoadOp loadOp, SDL_GPUStoreOp storeOp, const SDL_FColor *clearColor) {
5759

58-
assert(self->colorTexture);
60+
assert(index < self->numColorTargets);
61+
assert(self->colorTextures[index]);
5962

6063
SDL_GPUColorTargetInfo info = {
61-
.texture = self->colorTexture->texture,
64+
.texture = self->colorTextures[index]->texture,
6265
.load_op = loadOp,
6366
.store_op = storeOp,
6467
.clear_color = clearColor ? *clearColor : (SDL_FColor) { 0.f, 0.f, 0.f, 1.f },
6568
};
6669

6770
if (self->sampleCount > SDL_GPU_SAMPLECOUNT_1) {
68-
assert(self->resolveTexture);
71+
assert(self->resolveTextures[index]);
6972

7073
// Resolve the multisampled color into the single-sample resolve target. STORE is
7174
// promoted to RESOLVE_AND_STORE so the multisampled contents survive for any later
7275
// load-op pass (e.g. UI drawn over a 3D scene) while keeping the resolve current.
73-
info.resolve_texture = self->resolveTexture->texture;
76+
info.resolve_texture = self->resolveTextures[index]->texture;
7477
if (storeOp == SDL_GPU_STOREOP_STORE) {
7578
info.store_op = SDL_GPU_STOREOP_RESOLVE_AND_STORE;
7679
}
@@ -104,11 +107,16 @@ static Framebuffer *initWithDevice(Framebuffer *self, RenderDevice *device, cons
104107
assert(device);
105108
assert(info && info->size.w > 0 && info->size.h > 0);
106109

110+
assert(info->numColorTargets <= GPU_MAX_COLOR_TARGETS);
111+
107112
self = (Framebuffer *) super(Object, self, init);
108113
if (self) {
109114
self->device = retain(device);
110115

111-
self->colorFormat = info->colorFormat;
116+
self->numColorTargets = info->numColorTargets;
117+
for (Uint32 i = 0; i < info->numColorTargets; i++) {
118+
self->colorFormats[i] = info->colorFormats[i];
119+
}
112120
self->depthFormat = info->depthFormat;
113121
self->sampleCount = info->sampleCount;
114122

@@ -130,25 +138,27 @@ static bool resize(Framebuffer *self, const SDL_Size *size) {
130138
return false;
131139
}
132140

133-
release(self->colorTexture);
134-
release(self->resolveTexture);
141+
for (Uint32 i = 0; i < self->numColorTargets; i++) {
142+
release(self->colorTextures[i]);
143+
release(self->resolveTextures[i]);
144+
self->colorTextures[i] = NULL;
145+
self->resolveTextures[i] = NULL;
146+
}
135147
release(self->depthTexture);
136-
self->colorTexture = NULL;
137-
self->resolveTexture = NULL;
138148
self->depthTexture = NULL;
139149

140150
self->size = *size;
141151

142152
const bool multisampled = self->sampleCount > SDL_GPU_SAMPLECOUNT_1;
143153

144-
if (self->colorFormat != SDL_GPU_TEXTUREFORMAT_INVALID) {
154+
for (Uint32 i = 0; i < self->numColorTargets; i++) {
145155

146156
// A multisampled color target is resolved before sampling, so it needs only
147157
// COLOR_TARGET usage; the single-sample paths (no MSAA, or the resolve target)
148158
// also carry SAMPLER so they can be blit/sampled.
149-
self->colorTexture = $(self->device, createTexture, &(SDL_GPUTextureCreateInfo) {
159+
self->colorTextures[i] = $(self->device, createTexture, &(SDL_GPUTextureCreateInfo) {
150160
.type = SDL_GPU_TEXTURETYPE_2D,
151-
.format = self->colorFormat,
161+
.format = self->colorFormats[i],
152162
.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | (multisampled ? 0 : SDL_GPU_TEXTUREUSAGE_SAMPLER),
153163
.width = (Uint32) self->size.w,
154164
.height = (Uint32) self->size.h,
@@ -158,9 +168,9 @@ static bool resize(Framebuffer *self, const SDL_Size *size) {
158168
}, NULL);
159169

160170
if (multisampled) {
161-
self->resolveTexture = $(self->device, createTexture, &(SDL_GPUTextureCreateInfo) {
171+
self->resolveTextures[i] = $(self->device, createTexture, &(SDL_GPUTextureCreateInfo) {
162172
.type = SDL_GPU_TEXTURETYPE_2D,
163-
.format = self->colorFormat,
173+
.format = self->colorFormats[i],
164174
.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER,
165175
.width = (Uint32) self->size.w,
166176
.height = (Uint32) self->size.h,
@@ -188,11 +198,12 @@ static bool resize(Framebuffer *self, const SDL_Size *size) {
188198
}
189199

190200
/**
191-
* @fn Texture *Framebuffer::resolvedColorTexture(const Framebuffer *self)
201+
* @fn Texture *Framebuffer::resolvedColorTexture(const Framebuffer *self, Uint32 index)
192202
* @memberof Framebuffer
193203
*/
194-
static Texture *resolvedColorTexture(const Framebuffer *self) {
195-
return self->resolveTexture ?: self->colorTexture;
204+
static Texture *resolvedColorTexture(const Framebuffer *self, Uint32 index) {
205+
assert(index < self->numColorTargets);
206+
return self->resolveTextures[index] ?: self->colorTextures[index];
196207
}
197208

198209
#pragma mark - Class lifecycle

Sources/ObjectivelyGPU/Framebuffer.h

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ typedef struct Framebuffer Framebuffer;
3939
typedef struct FramebufferInterface FramebufferInterface;
4040
typedef struct Texture Texture;
4141

42+
/**
43+
* @brief The maximum number of color attachments a Framebuffer may have.
44+
* @details A render pass binds N color targets plus at most one depth-stencil target;
45+
* this caps N. Four matches the minimum guaranteed across backends.
46+
*/
47+
#define GPU_MAX_COLOR_TARGETS 4
48+
4249
/**
4350
* @brief Parameters for creating a Framebuffer.
4451
* @details The GPU-layer analogue of SDL's `*CreateInfo` structs, for a target that
@@ -56,18 +63,26 @@ typedef struct GPU_FramebufferCreateInfo {
5663
SDL_Size size;
5764

5865
/**
59-
* @brief Color attachment format, or `SDL_GPU_TEXTUREFORMAT_INVALID` to omit.
66+
* @brief The color attachment formats, one per render target (MRT).
67+
* @details Indices `[0, numColorTargets)` are used. With MSAA, every color attachment
68+
* is multisampled and gets its own single-sample resolve target.
69+
*/
70+
SDL_GPUTextureFormat colorFormats[GPU_MAX_COLOR_TARGETS];
71+
72+
/**
73+
* @brief The number of color attachments; `0` to omit color entirely.
6074
*/
61-
SDL_GPUTextureFormat colorFormat;
75+
Uint32 numColorTargets;
6276

6377
/**
6478
* @brief Depth attachment format, or `SDL_GPU_TEXTUREFORMAT_INVALID` to omit.
79+
* @details A render pass supports at most one depth-stencil target, so this is singular.
6580
*/
6681
SDL_GPUTextureFormat depthFormat;
6782

6883
/**
6984
* @brief MSAA sample count; `SDL_GPU_SAMPLECOUNT_1` for no multisampling.
70-
* @details When greater, a single-sample resolve target is allocated alongside the
85+
* @details When greater, a single-sample resolve target is allocated alongside each
7186
* multisampled color attachment.
7287
*/
7388
SDL_GPUSampleCount sampleCount;
@@ -88,12 +103,13 @@ typedef struct GPU_FramebufferCreateInfo {
88103
* @code
89104
* Framebuffer *fb = $(renderDevice, createFramebuffer, &(GPU_FramebufferCreateInfo) {
90105
* .size = { 1920, 1080 },
91-
* .colorFormat = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT,
106+
* .colorFormats = { SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT },
107+
* .numColorTargets = 1,
92108
* .depthFormat = SDL_GPU_TEXTUREFORMAT_D32_FLOAT,
93109
* .sampleCount = SDL_GPU_SAMPLECOUNT_1,
94110
* });
95111
*
96-
* SDL_GPUColorTargetInfo color = $(fb, colorTargetInfo,
112+
* SDL_GPUColorTargetInfo color = $(fb, colorTargetInfo, 0,
97113
* SDL_GPU_LOADOP_CLEAR, SDL_GPU_STOREOP_STORE,
98114
* &(SDL_FColor) { 0, 0, 0, 1 });
99115
*
@@ -127,38 +143,48 @@ struct Framebuffer {
127143
SDL_Size size;
128144

129145
/**
130-
* @brief The color attachment texture format, or `SDL_GPU_TEXTUREFORMAT_INVALID` if none.
146+
* @brief The MSAA sample count of all attachments.
147+
* @details `SDL_GPU_SAMPLECOUNT_1` for no multisampling. When greater, every color
148+
* attachment and the depth attachment are multisampled (a render pass requires a
149+
* single sample count across all attachments); each color attachment also gets a
150+
* single-sample resolve target in `resolveTextures`.
131151
*/
132-
SDL_GPUTextureFormat colorFormat;
152+
SDL_GPUSampleCount sampleCount;
133153

134154
/**
135-
* @brief The depth attachment texture format, or `SDL_GPU_TEXTUREFORMAT_INVALID` if none.
155+
* @brief The number of color attachments; indices `[0, numColorTargets)` are valid.
136156
*/
137-
SDL_GPUTextureFormat depthFormat;
157+
Uint32 numColorTargets;
138158

139159
/**
140-
* @brief The MSAA sample count of the color and depth attachments.
141-
* @details `SDL_GPU_SAMPLECOUNT_1` for no multisampling. When greater, `colorTexture`
142-
* is multisampled and `resolveTexture` holds the single-sample resolve.
160+
* @brief The color attachment texture formats, indices `[0, numColorTargets)`.
143161
*/
144-
SDL_GPUSampleCount sampleCount;
162+
SDL_GPUTextureFormat colorFormats[GPU_MAX_COLOR_TARGETS];
163+
164+
/**
165+
* @brief The color attachment textures, indices `[0, numColorTargets)`.
166+
* @details Multisampled when `sampleCount` > `SDL_GPU_SAMPLECOUNT_1`; in that case
167+
* sample/blit/present the corresponding `resolveTextures` entry, not these.
168+
*/
169+
Texture *colorTextures[GPU_MAX_COLOR_TARGETS];
145170

146171
/**
147-
* @brief The color attachment texture, or `NULL` if `colorFormat` is invalid.
148-
* @details Multisampled when `sampleCount` is greater than `SDL_GPU_SAMPLECOUNT_1`;
149-
* in that case sample the single-sample `resolveTexture`, not this texture.
172+
* @brief The single-sample resolve targets, or all `NULL` unless `sampleCount` > `SDL_GPU_SAMPLECOUNT_1`.
173+
* @details Render passes resolve `colorTextures[i]` into `resolveTextures[i]`; that is
174+
* the texture to sample, blit, or present. See `resolvedColorTexture`.
150175
*/
151-
Texture *colorTexture;
176+
Texture *resolveTextures[GPU_MAX_COLOR_TARGETS];
152177

153178
/**
154-
* @brief The single-sample resolve target, or `NULL` unless `sampleCount` is greater than `SDL_GPU_SAMPLECOUNT_1`.
155-
* @details Render passes resolve `colorTexture` into this texture; it is the texture
156-
* to sample, blit, or present. See `resolvedColorTexture`.
179+
* @brief The depth attachment texture format, or `SDL_GPU_TEXTUREFORMAT_INVALID` if none.
157180
*/
158-
Texture *resolveTexture;
181+
SDL_GPUTextureFormat depthFormat;
159182

160183
/**
161184
* @brief The depth attachment texture, or `NULL` if `depthFormat` is invalid.
185+
* @details Multisampled with the pass when `sampleCount` > `SDL_GPU_SAMPLECOUNT_1`. It is
186+
* not resolved: depth is used for testing and discarded. A consumer needing sampleable
187+
* resolved depth must resolve it in its own shader pass (SDL has no depth store-op resolve).
162188
* @private
163189
*/
164190
Texture *depthTexture;
@@ -181,18 +207,20 @@ struct FramebufferInterface {
181207
ObjectInterface objectInterface;
182208

183209
/**
184-
* @fn SDL_GPUColorTargetInfo Framebuffer::colorTargetInfo(const Framebuffer *self, SDL_GPULoadOp loadOp, SDL_GPUStoreOp storeOp, const SDL_FColor *clearColor)
185-
* @brief Returns a populated `SDL_GPUColorTargetInfo` for this framebuffer's color attachment.
186-
* @details Pass the result directly to `CommandBuffer::beginRenderPass`.
187-
* `assert`s that `colorTexture` is non-NULL.
210+
* @fn SDL_GPUColorTargetInfo Framebuffer::colorTargetInfo(const Framebuffer *self, Uint32 index, SDL_GPULoadOp loadOp, SDL_GPUStoreOp storeOp, const SDL_FColor *clearColor)
211+
* @brief Returns a populated `SDL_GPUColorTargetInfo` for color attachment @p index.
212+
* @details Assemble an array of these (one per color target) and pass it to
213+
* `CommandBuffer::beginRenderPass`. When multisampled, the resolve target and a
214+
* `RESOLVE_AND_STORE` store op are wired in automatically. `assert`s @p index is valid.
188215
* @param self The Framebuffer.
216+
* @param index The color attachment index, in `[0, numColorTargets)`.
189217
* @param loadOp Load operation at the start of the pass.
190218
* @param storeOp Store operation at the end of the pass.
191219
* @param clearColor Clear color used when `loadOp` is `SDL_GPU_LOADOP_CLEAR`, or NULL for black.
192220
* @return A stack-allocated `SDL_GPUColorTargetInfo`.
193221
* @memberof Framebuffer
194222
*/
195-
SDL_GPUColorTargetInfo (*colorTargetInfo)(const Framebuffer *self,
223+
SDL_GPUColorTargetInfo (*colorTargetInfo)(const Framebuffer *self, Uint32 index,
196224
SDL_GPULoadOp loadOp, SDL_GPUStoreOp storeOp, const SDL_FColor *clearColor);
197225

198226
/**
@@ -222,14 +250,15 @@ struct FramebufferInterface {
222250
Framebuffer *(*initWithDevice)(Framebuffer *self, RenderDevice *device, const GPU_FramebufferCreateInfo *info);
223251

224252
/**
225-
* @fn Texture *Framebuffer::resolvedColorTexture(const Framebuffer *self)
226-
* @brief Returns the single-sample color texture to sample, blit, or present.
227-
* @details Returns `resolveTexture` when multisampled, otherwise `colorTexture`.
253+
* @fn Texture *Framebuffer::resolvedColorTexture(const Framebuffer *self, Uint32 index)
254+
* @brief Returns the single-sample color texture for attachment @p index, to sample, blit, or present.
255+
* @details Returns `resolveTextures[index]` when multisampled, otherwise `colorTextures[index]`.
228256
* @param self The Framebuffer.
229-
* @return The resolved color texture, or `NULL` if this framebuffer has no color attachment.
257+
* @param index The color attachment index, in `[0, numColorTargets)`.
258+
* @return The resolved color texture, or `NULL` if @p index has no attachment.
230259
* @memberof Framebuffer
231260
*/
232-
Texture *(*resolvedColorTexture)(const Framebuffer *self);
261+
Texture *(*resolvedColorTexture)(const Framebuffer *self, Uint32 index);
233262

234263
/**
235264
* @fn bool Framebuffer::resize(Framebuffer *self, const SDL_Size *size)

Sources/ObjectivelyGPU/RenderDevice.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ static void endFrame(RenderDevice *self) {
127127

128128
GPU_Assert(self->commandBuffer, "endFrame called without a frame in flight");
129129

130-
Texture *color = $(self->framebuffer, resolvedColorTexture);
130+
Texture *color = $(self->framebuffer, resolvedColorTexture, 0);
131131
GPU_Assert(color, "framebuffer has no color attachment to present");
132132

133133
$(self->commandBuffer, blitTexture, &(SDL_GPUBlitInfo) {

0 commit comments

Comments
 (0)