Skip to content

Commit

Permalink
Continued working on BindGroup cycling when necessary. Samplers are v…
Browse files Browse the repository at this point in the history
…isibly changing, just not to what I expected :(
  • Loading branch information
klukaszek committed Nov 7, 2024
1 parent 94ae9d9 commit 1adb105
Showing 1 changed file with 97 additions and 70 deletions.
167 changes: 97 additions & 70 deletions src/gpu/webgpu/SDL_gpu_webgpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
// License: Zlib
// Description: WebGPU driver for SDL_gpu using the emscripten WebGPU implementation
// Note: Compiling SDL GPU programs using emscripten will require -sUSE_WEBGPU=1 -sASYNCIFY=1
//
// TODO: Continue implementing BindGroupLayouts and BindGroups
// TODO: Consider assigning a NULL PipelineResources to the Pipeline. When we are binding any bindings to the pipeline, we permanently assign the data to the respective PipelineResources->BindGroup->BindGroupEntry struct. Bindings that have a cycle flag will be updated on subsequent calls to the respective Bind function, but will skip all other bindings that were not marked to cycle.

#include "../SDL_sysgpu.h"
#include "SDL_internal.h"
Expand Down Expand Up @@ -200,6 +197,7 @@ typedef struct WebGPUBindGroup
WGPUBindGroup bindGroup;
WGPUBindGroupEntry *entries;
uint32_t entryCount;
bool cycleBindings;
} WebGPUBindGroup;

// ---------------------------------------------------
Expand Down Expand Up @@ -331,6 +329,16 @@ typedef struct WebGPUGraphicsPipeline
WebGPUShader *vertexShader;
WebGPUShader *fragmentShader;
WGPURenderPipelineDescriptor pipelineDesc;

// If BindGPUFragmentSamplers is called, this will be the hash representing all bindings provided to the function
// Whenever BindGPUFragmentSamplers is called, if the hash matches the current hash, we can skip creating a new bind group
size_t bindSamplerHash;
size_t bindXXXXHash; // Reserved for future use
size_t bindYYYYHash; // Reserved for future use
size_t bindZZZZHash; // Reserved for future use

bool cycleBindGroups;

SDL_AtomicInt referenceCount;
} WebGPUGraphicsPipeline;

Expand Down Expand Up @@ -1178,7 +1186,6 @@ static SDL_GPUCommandBuffer *WebGPU_AcquireCommandBuffer(SDL_GPURenderer *driver
height = renderer->claimedWindows[0]->window->h;
commandBuffer->currentViewport = (WebGPUViewport){ 0, 0, width, height, 0.0, 1.0 };
commandBuffer->currentScissor = (WebGPURect){ 0, 0, width, height };
commandBuffer->bindGroups = SDL_malloc(sizeof(WebGPUBindGroup) * 8);

WGPUCommandEncoderDescriptor commandEncoderDesc = {
.label = "SDL_GPU Command Encoder",
Expand All @@ -1198,25 +1205,28 @@ static bool WebGPU_Submit(SDL_GPUCommandBuffer *commandBuffer)
.label = "SDL_GPU Command Buffer",
};

// Finish the command buffer and submit it to the queue
WGPUCommandBuffer commandHandle = wgpuCommandEncoderFinish(wgpuCmdBuf->commandEncoder, &commandBufferDesc);
if (!commandHandle) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to finish command buffer");
return false;
}
wgpuQueueSubmit(renderer->queue, 1, &commandHandle);

// Release the actual command buffer followed by the SDL command buffer
// Release the actual command buffer and command encoder
wgpuCommandBufferRelease(commandHandle);
wgpuCommandEncoderRelease(wgpuCmdBuf->commandEncoder);

// Release any bind groups that were created
if (wgpuCmdBuf->bindGroups) {
for (Uint32 i = 0; i < wgpuCmdBuf->bindGroupCount; i += 1) {
/*wgpuBindGroupRelease(wgpuCmdBuf->bindGroups[i].bindGroup);*/
/*SDL_aligned_free(wgpuCmdBuf->bindGroups[i].entries);*/
// We don't free the BindGroup as it is owned by the pipeline and not the cmdBuffer
SDL_aligned_free(wgpuCmdBuf->bindGroups[i].entries);
}
SDL_free(wgpuCmdBuf->bindGroups);
}

// Release the memory for the command buffer
SDL_free(wgpuCmdBuf);

return true;
Expand Down Expand Up @@ -2473,16 +2483,24 @@ static SDL_GPUGraphicsPipeline *WebGPU_CreateGraphicsPipeline(
}
pipeline->resourceLayout = resourceLayout;

// Keep this null until we have a set of BindGroupEntries to store in the pipeline
// This initial set of BindGroups is stored directly in the WebGPUCommandBuffer when the SDL_GPU_Bind* functions are called
// Once we the render pass is called, we save a copy of our BindGroupEntries to our pipeline to reuse them later if needed.
// We can then use the BindGroupEntries to create the WGPUBindGroups for the pipeline.
// Now that the WGPUBindGroups exist and are saved within this struct, we no longer have to build them again. (As long as you don't need to change the binding references!!!!!!!)
// BIG LIMITATION: This means that if you want to change the bindings, you will have to create a new pipeline.
pipeline->bindGroups = NULL;
// Preconstruct the bind groups for the pipeline.
pipeline->bindGroups = SDL_malloc(sizeof(WebGPUBindGroup) * resourceLayout->bindGroupLayoutCount);
for (Uint32 i = 0; i < resourceLayout->bindGroupLayoutCount; i += 1) {
pipeline->bindGroups[i].bindGroup = NULL;
}

pipeline->bindGroupCount = resourceLayout->bindGroupLayoutCount;
SDL_Log("bindGroupCount: %u", pipeline->bindGroupCount);

// Used to determine when to cycle the bind groups if any of the bindings have changed
pipeline->bindSamplerHash = 0;
pipeline->bindXXXXHash = 0;
pipeline->bindYYYYHash = 0;
pipeline->bindZZZZHash = 0;

// set to true for the first frame to ensure bind groups are created
pipeline->cycleBindGroups = true;

SDL_Log("Created Resource Layout");

WebGPUShader *vertShader = (WebGPUShader *)pipelineCreateInfo->vertex_shader;
Expand Down Expand Up @@ -2795,6 +2813,9 @@ static void WebGPU_SetTextureName(
wgpuTextureViewSetLabel(webgpuTexture->fullView, name);
}

// TODO: For buffers that meet the alignment requirements, we can use a wgpuQueueCopyBufferToTexture() instead.
// For this to work, the row pitch must be a multiple of 256 bytes.
// Otherwise we will just call wgpuQueueWriteTexture() as we do now.
static void WebGPU_UploadToTexture(SDL_GPUCommandBuffer *commandBuffer,
const SDL_GPUTextureTransferInfo *source,
const SDL_GPUTextureRegion *destination,
Expand All @@ -2817,7 +2838,7 @@ static void WebGPU_UploadToTexture(SDL_GPUCommandBuffer *commandBuffer,

WGPUTextureDataLayout dataLayout = {
.offset = source->offset,
.bytesPerRow = destination->w * 4, // Keep this simple, no alignment
.bytesPerRow = destination->w * 4, // 4 bytes per RGBA8 pixel
.rowsPerImage = destination->h,
};

Expand All @@ -2830,29 +2851,15 @@ static void WebGPU_UploadToTexture(SDL_GPUCommandBuffer *commandBuffer,
.y = destination->y,
.z = destination->z,
},
.aspect = WGPUTextureAspect_All // Add this explicitly
.aspect = WGPUTextureAspect_All,
};

WGPUExtent3D extent = {
.width = destination->w,
.height = destination->h,
.depthOrArrayLayers = 1,
.depthOrArrayLayers = destination->d,
};

// Add debug logging
SDL_Log("Transfer buffer size: %llu", transfer_buffer->size);
SDL_Log("Expected data size: %u", destination->w * destination->h * 4);
SDL_Log("Bytes per row: %u", dataLayout.bytesPerRow);

// Iterate over the buffer data and print the RGBA values for each pixel
Uint8 *data = (Uint8 *)transfer_buffer->mappedData;
for (Uint32 i = 0; i < destination->h; i += 1) {
for (Uint32 j = 0; j < destination->w; j += 1) {
Uint32 index = (i * destination->w + j) * 4;
SDL_Log("RGBA: %u %u %u %u", data[index], data[index + 1], data[index + 2], data[index + 3]);
}
}

wgpuQueueWriteTexture(
wgpuCmdBuf->renderer->queue,
&copyTexture,
Expand Down Expand Up @@ -2935,25 +2942,43 @@ static void WebGPU_BindFragmentSamplers(SDL_GPUCommandBuffer *commandBuffer,
SDL_LogError(SDL_LOG_CATEGORY_GPU, "No current graphics pipeline set");
return;
}
if (wgpuCmdBuf->currentGraphicsPipeline->resourceLayout == NULL) {
WebGPUGraphicsPipeline *pipeline = wgpuCmdBuf->currentGraphicsPipeline;
if (pipeline->resourceLayout == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "No resource layout set for current graphics pipeline");
return;
}

WebGPUGraphicsPipeline *pipeline = wgpuCmdBuf->currentGraphicsPipeline;
if (pipeline->bindGroups != NULL) {
// Early return if the bind groups have already been created
// We can do this because if the proper bindings do not exist within the bind groups, the renderpass will fail
// Our BindGroups should be immutable once they are created, so we can skip this step if they already exist
void **pointers = SDL_malloc(sizeof(void *) * numBindings * 2);
if (pointers == NULL) {
SDL_OutOfMemory();
return;
}

for (Uint32 i = 0; i < numBindings; i += 2) {
pointers[i] = textureSamplerBindings[i].sampler;
pointers[i + 1] = textureSamplerBindings[i].texture;
}

size_t hash = 0;
for (Uint32 i = 0; i < numBindings; i += 1) {
hash ^= (size_t)((const void *const *)pointers[i]);
hash *= 0x9e3779b9;
}

// If the hash is different, we need to cycle the bind groups
if (pipeline->bindSamplerHash == 0) {
pipeline->bindSamplerHash = hash;
} else if (pipeline->bindSamplerHash != hash) {
SDL_Log("Cycling bind groups due to change in fragment samplers");
pipeline->cycleBindGroups = true;
pipeline->bindSamplerHash = hash;
}

// Get our bind group layout from the pipeline resource layout
WebGPUPipelineResourceLayout *resourceLayout = wgpuCmdBuf->currentGraphicsPipeline->resourceLayout;
WebGPUPipelineResourceLayout *resourceLayout = pipeline->resourceLayout;
WebGPUBindGroupLayout *bgLayouts = resourceLayout->bindGroupLayouts;
Uint32 bgLayoutCount = resourceLayout->bindGroupLayoutCount;

SDL_Log("Creating Bind Group Entries for Pipeline");
// Allocate memory for the bind groups in the command buffer
for (Uint32 i = 0; i < bgLayoutCount; i += 1) {
wgpuCmdBuf->bindGroups[i].entries = SDL_aligned_alloc(alignof(WGPUBindGroupEntry), sizeof(WGPUBindGroupEntry) * resourceLayout->bindGroupLayouts[i].bindingCount);
Expand All @@ -2976,7 +3001,6 @@ static void WebGPU_BindFragmentSamplers(SDL_GPUCommandBuffer *commandBuffer,
// Find the associated entry in the bind group layout
for (Uint32 j = 0; j < layout->bindingCount; j += 1) {
WebGPUBindingInfo *layBinding = &layout->bindings[j];

// If the binding is a sampler, we need to create a bind group entry for it
// Save this entry in the current resources for later use when we build the bind group
if (layBinding->type == WGPUBindingType_Sampler) {
Expand All @@ -2992,7 +3016,6 @@ static void WebGPU_BindFragmentSamplers(SDL_GPUCommandBuffer *commandBuffer,
.binding = layBinding->binding,
.sampler = sampler->sampler,
};
wgpuCmdBuf->bindGroups[i].entryCount += 1;
currentSampler += 1;
}
} else if (layBinding->type == WGPUBindingType_Texture) {
Expand All @@ -3004,7 +3027,6 @@ static void WebGPU_BindFragmentSamplers(SDL_GPUCommandBuffer *commandBuffer,
.binding = layBinding->binding,
.textureView = container->activeTextureHandle->webgpuTexture->fullView,
};
wgpuCmdBuf->bindGroups[i].entryCount += 1;
}
}
}
Expand Down Expand Up @@ -3106,38 +3128,49 @@ static void WebGPU_INTERNAL_SetBindGroups(SDL_GPUCommandBuffer *commandBuffer)
// If so, we need to set them before we begin the render pass
if (pipeline != NULL) {
Uint32 numBindGroups = wgpuCmdBuf->bindGroupCount;
WebGPUBindGroup *commandBindGroups = wgpuCmdBuf->bindGroups;
WebGPUBindGroup *cmdBindGroups = wgpuCmdBuf->bindGroups;
WebGPUPipelineResourceLayout *resourceLayout = wgpuCmdBuf->currentGraphicsPipeline->resourceLayout;

if (numBindGroups != 0) {
// Check if we need to cycle the bind groups (if any of the bindings have changed or first load)
if (numBindGroups != 0 && pipeline->cycleBindGroups == true) {

// If our pipeline expects bind groups, but we don't have any, copy the bind groups from the command buffer over
if (pipeline->bindGroups == NULL) {
SDL_Log("Copying bind groups from command buffer to pipeline");
pipeline->bindGroups = SDL_malloc(sizeof(WebGPUBindGroup) * numBindGroups);
SDL_memcpy(pipeline->bindGroups, commandBindGroups, sizeof(WebGPUBindGroup) * numBindGroups);
// If the WGPUBindGroup does not exist, we need to create it so that we can set it on the render pass
for (Uint32 i = 0; i < numBindGroups; i += 1) {

// Iterate over the bind groups and set them on the render pass
// If the WGPUBindGroup does not exist, we need to create it so that we can set it on the render pass
for (Uint32 i = 0; i < numBindGroups; i += 1) {
SDL_Log("Creating bind group %u", i);
// Release the bind group and free the entries (if they already exist)
if (pipeline->bindGroups[i].bindGroup != NULL) {
SDL_Log("Releasing bind group %u", i);

for (Uint32 j = 0; j < resourceLayout->bindGroupLayouts[i].bindingCount; j += 1) {
SDL_Log("Bind group %u, entry %u: %d", i, j, pipeline->bindGroups[i].entries[j].binding);
}

WGPUBindGroupDescriptor bindGroupDesc = {
.layout = resourceLayout->bindGroupLayouts[i].layout,
.entryCount = pipeline->bindGroups[i].entryCount,
.entries = pipeline->bindGroups[i].entries,
};
pipeline->bindGroups[i].bindGroup = wgpuDeviceCreateBindGroup(wgpuCmdBuf->renderer->device, &bindGroupDesc);
// TODO - This is causing a crash, need to implement a way to grab
// any bind groups that are currently in use, store them, and then release them
// once RenderPassEncoder is finished with them (RenderPassEncoderEnd crashes).
// This gives us the opportunity to use the new bind groups with the next frame,
// and keep the current frame's bind groups until the frame is finished.

SDL_Log("Bind group %p created", pipeline->bindGroups[i].bindGroup);
/*wgpuBindGroupRelease(pipeline->bindGroups[i].bindGroup);*/
/*SDL_aligned_free(pipeline->bindGroups[i].entries);*/
/*pipeline->bindGroups[i].entryCount = 0;*/
}

// Copy the bind group over to the pipeline so we don't have to recreate it every frame
SDL_memcpy(&pipeline->bindGroups[i], &cmdBindGroups[i], sizeof(WebGPUBindGroup));

WGPUBindGroupDescriptor bindGroupDesc = {
.layout = resourceLayout->bindGroupLayouts[i].layout,
.entryCount = resourceLayout->bindGroupLayouts[i].bindingCount,
.entries = pipeline->bindGroups[i].entries,
};

// Assign the bind group to the pipeline so that we can reuse it without recreating it.
pipeline->bindGroups[i].bindGroup = wgpuDeviceCreateBindGroup(wgpuCmdBuf->renderer->device, &bindGroupDesc);
pipeline->bindGroups[i].entryCount = resourceLayout->bindGroupLayouts[i].bindingCount;
}

// Reset the cycle flag
pipeline->cycleBindGroups = false;
}

// Iterate over the pipeline bind groups and set them for the render pass
for (Uint32 i = 0; i < numBindGroups; i += 1) {
Uint32 group = resourceLayout->bindGroupLayouts[i].group;
wgpuRenderPassEncoderSetBindGroup(wgpuCmdBuf->renderPassEncoder, group, pipeline->bindGroups[i].bindGroup, 0, 0);
Expand Down Expand Up @@ -3285,16 +3318,12 @@ static SDL_GPUDevice *WebGPU_CreateDevice(bool debug, bool preferLowPower, SDL_P
result->CreateShader = WebGPU_CreateShader;
result->ReleaseShader = WebGPU_ReleaseShader;

// TODO START (finish the implementation of these functions)

result->CreateGraphicsPipeline = WebGPU_CreateGraphicsPipeline;
result->BindGraphicsPipeline = WebGPU_BindGraphicsPipeline;
result->ReleaseGraphicsPipeline = WebGPU_ReleaseGraphicsPipeline;
result->DrawPrimitives = WebGPU_DrawPrimitives;
result->DrawIndexedPrimitives = WebGPU_DrawIndexedPrimitives;

// TODO END

result->SetScissor = WebGPU_SetScissorRect;
result->SetViewport = WebGPU_SetViewport;
result->SetStencilReference = WebGPU_SetStencilReference;
Expand All @@ -3308,8 +3337,6 @@ static SDL_GPUDevice *WebGPU_CreateDevice(bool debug, bool preferLowPower, SDL_P
return result;
}

// TODO: Implement other necessary functions like WebGPU_DestroyDevice, WebGPU_CreateTexture, etc.

SDL_GPUBootstrap WebGPUDriver = {
"webgpu",
SDL_GPU_SHADERFORMAT_WGSL,
Expand Down

0 comments on commit 1adb105

Please sign in to comment.