Skip to content

Commit fa21e4b

Browse files
committed
Support all pixel formats, including those with pitch / stride.
1 parent 22295c8 commit fa21e4b

2 files changed

Lines changed: 71 additions & 41 deletions

File tree

Sources/ObjectivelyGPU/Texture.c

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,52 @@ static void dealloc(Object *self) {
5050

5151
#pragma mark - Texture
5252

53+
/**
54+
* @brief Uploads @p size bytes of pixel data into mip level 0 of this texture.
55+
* @details The source data is laid out at @p pixelsPerRow pixels (texels) per row, so
56+
* callers can pass strided data (e.g. an `SDL_Surface` with row padding) without first
57+
* tightening it. The texture's dimensions and layer count come from its metadata, so
58+
* this must be called after `initWithDevice` has populated them.
59+
*/
60+
static void uploadPixels(Texture *self, const void *pixels, Uint32 size, Uint32 pixelsPerRow) {
61+
62+
SDL_GPUDevice *device = self->device->device;
63+
64+
SDL_GPUTransferBuffer *tbuf = SDL_CreateGPUTransferBuffer(device, &(SDL_GPUTransferBufferCreateInfo) {
65+
.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
66+
.size = size,
67+
});
68+
GPU_Assert(tbuf, "SDL_CreateGPUTransferBuffer");
69+
70+
void *mapped = SDL_MapGPUTransferBuffer(device, tbuf, false);
71+
GPU_Assert(mapped, "SDL_MapGPUTransferBuffer");
72+
73+
memcpy(mapped, pixels, size);
74+
SDL_UnmapGPUTransferBuffer(device, tbuf);
75+
76+
CommandBuffer *commands = $(self->device, acquireCommandBuffer);
77+
CopyPass *copyPass = $(commands, beginCopyPass);
78+
79+
$(copyPass, uploadTexture,
80+
&(SDL_GPUTextureTransferInfo) {
81+
.transfer_buffer = tbuf,
82+
.pixels_per_row = pixelsPerRow,
83+
.rows_per_layer = (Uint32) self->size.h,
84+
},
85+
&(SDL_GPUTextureRegion) {
86+
.texture = self->texture,
87+
.w = (Uint32) self->size.w,
88+
.h = (Uint32) self->size.h,
89+
.d = self->layerCountOrDepth,
90+
},
91+
false);
92+
93+
release(copyPass);
94+
$(commands, submit);
95+
release(commands);
96+
SDL_ReleaseGPUTransferBuffer(device, tbuf);
97+
}
98+
5399
/**
54100
* @fn Texture *Texture::initWithDevice(Texture *self, RenderDevice *device, const SDL_GPUTextureCreateInfo *info, const void *pixels)
55101
* @memberof Texture
@@ -76,42 +122,13 @@ static Texture *initWithDevice(Texture *self, RenderDevice *device, const SDL_GP
76122
self->sampleCount = info->sample_count;
77123

78124
if (pixels) {
79-
const Uint32 bytesPerTexel = SDL_GPUTextureFormatTexelBlockSize(info->format);
80-
const Uint32 totalBytes = info->width * info->height * info->layer_count_or_depth * bytesPerTexel;
81-
82-
SDL_GPUTransferBuffer *tbuf = SDL_CreateGPUTransferBuffer(device->device, &(SDL_GPUTransferBufferCreateInfo) {
83-
.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
84-
.size = totalBytes,
85-
});
86-
GPU_Assert(tbuf, "SDL_CreateGPUTransferBuffer");
87-
88-
void *mapped = SDL_MapGPUTransferBuffer(device->device, tbuf, false);
89-
GPU_Assert(mapped, "SDL_MapGPUTransferBuffer");
90-
91-
memcpy(mapped, pixels, totalBytes);
92-
SDL_UnmapGPUTransferBuffer(device->device, tbuf);
93-
94-
CommandBuffer *commands = $(device, acquireCommandBuffer);
95-
CopyPass *copyPass = $(commands, beginCopyPass);
96-
97-
$(copyPass, uploadTexture,
98-
&(SDL_GPUTextureTransferInfo) {
99-
.transfer_buffer = tbuf,
100-
.pixels_per_row = info->width,
101-
.rows_per_layer = info->height,
102-
},
103-
&(SDL_GPUTextureRegion) {
104-
.texture = self->texture,
105-
.w = info->width,
106-
.h = info->height,
107-
.d = info->layer_count_or_depth,
108-
},
109-
false);
110-
111-
release(copyPass);
112-
$(commands, submit);
113-
release(commands);
114-
SDL_ReleaseGPUTransferBuffer(device->device, tbuf);
125+
// Tightly-packed source: SDL_CalculateGPUTextureFormatSize accounts for
126+
// block-compressed formats, so this is correct for BCn/ASTC/etc. as well as
127+
// plain texel formats (where a naive width*height*texelSize would also work).
128+
const Uint32 size = SDL_CalculateGPUTextureFormatSize(info->format,
129+
info->width, info->height, info->layer_count_or_depth);
130+
131+
uploadPixels(self, pixels, size, info->width);
115132
}
116133
}
117134

@@ -132,6 +149,9 @@ static Texture *initWithSurface(Texture *self, RenderDevice *device, SDL_Surface
132149
: SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
133150
GPU_Assert(rgba, "SDL_ConvertSurface");
134151

152+
// Create the texture empty, then upload respecting the surface's row pitch — an
153+
// SDL_Surface's pitch is often larger than width * bytesPerPixel (alignment, or a
154+
// sub-surface of an atlas), so assuming tightly-packed rows would skew the image.
135155
self = $(self, initWithDevice, device, &(SDL_GPUTextureCreateInfo) {
136156
.type = SDL_GPU_TEXTURETYPE_2D,
137157
.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
@@ -141,7 +161,14 @@ static Texture *initWithSurface(Texture *self, RenderDevice *device, SDL_Surface
141161
.layer_count_or_depth = 1,
142162
.num_levels = 1,
143163
.sample_count = SDL_GPU_SAMPLECOUNT_1,
144-
}, rgba->pixels);
164+
}, NULL);
165+
166+
if (self) {
167+
const Uint32 bytesPerPixel = SDL_BYTESPERPIXEL(rgba->format);
168+
uploadPixels(self, rgba->pixels,
169+
(Uint32) rgba->pitch * (Uint32) rgba->h,
170+
(Uint32) rgba->pitch / bytesPerPixel);
171+
}
145172

146173
if (rgba != surface) {
147174
SDL_DestroySurface(rgba);

Sources/ObjectivelyGPU/Texture.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,13 @@ struct TextureInterface {
133133
* @details This is the designated initializer. When @p pixels is non-NULL, a
134134
* temporary upload transfer buffer is allocated, @p pixels is copied into it,
135135
* a copy pass is recorded and submitted, so the texture is GPU-resident before
136-
* returning. Only mip level 0 is uploaded.
136+
* returning. Only mip level 0 is uploaded. The upload size is computed with
137+
* `SDL_CalculateGPUTextureFormatSize`, so block-compressed formats (BCn, ASTC, …)
138+
* are handled correctly; @p pixels must be tightly packed for @p info's format.
137139
* @param self The Texture.
138140
* @param device The RenderDevice used to create and release the texture. Retained.
139141
* @param info Texture creation parameters (format, type, dimensions, usage, etc.).
140-
* @param pixels Initial pixel data to upload, or `NULL` to leave the texture uninitialised.
142+
* @param pixels Initial tightly-packed pixel data to upload, or `NULL` to leave the texture uninitialised.
141143
* @return The initialized Texture, or `NULL` on failure.
142144
* @memberof Texture
143145
*/
@@ -147,8 +149,9 @@ struct TextureInterface {
147149
* @fn Texture *Texture::initWithSurface(Texture *self, RenderDevice *device, SDL_Surface *surface, SDL_GPUTextureUsageFlags usage)
148150
* @brief Initializes this Texture from an `SDL_Surface`, uploading its pixels immediately.
149151
* @details Converts @p surface to `SDL_PIXELFORMAT_RGBA32` if needed, derives the
150-
* dimensions from the surface, and delegates to `initWithDevice` with
151-
* `SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM`. The surface is neither modified nor freed.
152+
* dimensions from the surface, and uploads as `SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM`.
153+
* The surface's row `pitch` is respected, so padded rows (alignment, or a sub-surface
154+
* of an atlas) upload correctly. The surface is neither modified nor freed.
152155
* @param self The Texture.
153156
* @param device The RenderDevice used to create and release the texture. Retained.
154157
* @param surface The source surface. Must not be NULL.

0 commit comments

Comments
 (0)