Files
Mikemon/src/renderer_wgpu.c
T
Sven Balzer 6a984b40d9 remove vertex_buffer and index_buffer for characters and character shadows
refactor world shader vertex position calculation
2026-08-05 20:48:57 +02:00

1096 lines
43 KiB
C

#include "renderer.h"
#include <webgpu/webgpu.h>
#include <dcimgui_impl_wgpu.h>
typedef struct R_Buffer_Impl {
WGPUBuffer buffer;
Uint32 num_bytes;
R_Buffer_Usage usage;
} R_Buffer_Impl;
typedef struct R_Texture_Impl {
WGPUTexture texture;
WGPUTextureView view;
Uint32 width;
Uint32 height;
bool multisampled;
R_Texture_Format format;
} R_Texture_Impl;
R_Texture R_surface;
static bool init_done;
static SDL_Window *window;
static WGPUInstance instance;
static WGPUDevice device;
static WGPUQueue queue;
static WGPUSurface surface;
static WGPUSurfaceConfiguration surface_configuration;
static R_Texture_Impl surface_texture;
static R_Texture framebuffer;
static WGPUCommandEncoder command_encoder;
static WGPURenderPassEncoder pass_encoder;
static WGPURenderPipeline shaders[R_SHADER_COUNT];
static R_Shader current_shader;
static WGPUSampler bilinear_sampler;
static WGPUTextureFormat texture_formats[] = {
[R_TEXTURE_FORMAT_R8_UNORM] = WGPUTextureFormat_R8Unorm,
[R_TEXTURE_FORMAT_R16_UINT] = WGPUTextureFormat_R16Uint,
[R_TEXTURE_FORMAT_RGBA8_UNORM] = WGPUTextureFormat_RGBA8Unorm,
[R_TEXTURE_FORMAT_RGBA8_UNORM_SRGB] = WGPUTextureFormat_RGBA8UnormSrgb,
[R_TEXTURE_FORMAT_BGRA8_UNORM] = WGPUTextureFormat_BGRA8Unorm,
[R_TEXTURE_FORMAT_BGRA8_UNORM_SRGB] = WGPUTextureFormat_BGRA8UnormSrgb,
};
static_assert(SDL_arraysize(texture_formats) == R_TEXTURE_FORMAT_COUNT);
static Uint32 texel_sizes[] = {
[R_TEXTURE_FORMAT_R8_UNORM] = 1,
[R_TEXTURE_FORMAT_R16_UINT] = 2,
[R_TEXTURE_FORMAT_RGBA8_UNORM] = 4,
[R_TEXTURE_FORMAT_RGBA8_UNORM_SRGB] = 4,
[R_TEXTURE_FORMAT_BGRA8_UNORM] = 4,
[R_TEXTURE_FORMAT_BGRA8_UNORM_SRGB] = 4,
};
static_assert(SDL_arraysize(texel_sizes) == R_TEXTURE_FORMAT_COUNT);
static WGPUTextureUsage texture_usages[] = {
[R_TEXTURE_USAGE_SAMPLED] = WGPUTextureUsage_TextureBinding,
[R_TEXTURE_USAGE_STORAGE] = WGPUTextureUsage_TextureBinding,
[R_TEXTURE_USAGE_TARGET] = WGPUTextureUsage_RenderAttachment,
};
static_assert(SDL_arraysize(texture_usages) == R_TEXTURE_USAGE_COUNT);
static WGPUBufferUsage buffer_usages[] = {
[R_BUFFER_USAGE_UNIFORM] = WGPUBufferUsage_Uniform,
[R_BUFFER_USAGE_STORAGE] = WGPUBufferUsage_Storage,
[R_BUFFER_USAGE_VERTEX] = WGPUBufferUsage_Vertex,
[R_BUFFER_USAGE_INDEX] = WGPUBufferUsage_Index,
};
static_assert(SDL_arraysize(buffer_usages) == R_BUFFER_USAGE_COUNT);
static WGPULoadOp load_ops[] = {
[R_LOAD_OP_LOAD] = WGPULoadOp_Load,
[R_LOAD_OP_CLEAR] = WGPULoadOp_Clear,
};
static_assert(SDL_arraysize(load_ops) == R_LOAD_OP_COUNT);
static WGPUStoreOp store_ops[] = {
[R_STORE_OP_STORE] = WGPUStoreOp_Store,
[R_STORE_OP_DISCARD] = WGPUStoreOp_Discard,
};
static_assert(SDL_arraysize(store_ops) == R_STORE_OP_COUNT);
R_Texture renderer_texture_create(R_Texture_Format format, R_Texture_Usage usage, bool multisampled, Uint32 width, Uint32 height, void *data, const char *debug_name) {
WGPUTextureDescriptor descriptor = {
.label = { .data = debug_name, .length = WGPU_STRLEN },
.usage = texture_usages[usage] | WGPUTextureUsage_CopyDst,
.dimension = WGPUTextureDimension_2D,
.size = { .width = width, .height = height, .depthOrArrayLayers = 1 },
.format = texture_formats[format],
.mipLevelCount = 1,
.sampleCount = multisampled ? 4 : 1,
.viewFormatCount = 0,
.viewFormats = NULL,
};
WGPUTexture texture = wgpuDeviceCreateTexture(device, &descriptor);
if (!texture) return NULL;
WGPUTextureView view = wgpuTextureCreateView(texture, NULL);
if (!view) {
wgpuTextureRelease(texture);
return NULL;
}
if (data) {
Uint32 texel_size = texel_sizes[format];
WGPUTexelCopyTextureInfo destination = {
.texture = texture,
.mipLevel = 0,
.origin = { .x = 0, .y = 0, .z = 0 },
.aspect = WGPUTextureAspect_All,
};
WGPUTexelCopyBufferLayout data_layout = {
.offset = 0,
.bytesPerRow = width * texel_size,
.rowsPerImage = height,
};
WGPUExtent3D extent = {
.width = width,
.height = height,
.depthOrArrayLayers = 1,
};
wgpuQueueWriteTexture(queue, &destination, data, width * height * texel_size, &data_layout, &extent);
}
R_Texture_Impl *result = SDL_calloc(1, sizeof(R_Texture_Impl));
if (!result) {
wgpuTextureViewRelease(view);
wgpuTextureRelease(texture);
return NULL;
}
*result = (R_Texture_Impl){
.texture = texture,
.view = view,
.width = width,
.height = height,
.multisampled = multisampled,
.format = format,
};
return result;
}
void renderer_texture_update(R_Texture texture, Uint32 offset_x, Uint32 offset_y, Uint32 width, Uint32 height, void *data, Uint32 bytes_per_row) {
WGPUTexelCopyTextureInfo info = {
.texture = texture->texture,
.mipLevel = 0,
.origin = { offset_x, offset_y, 0 },
.aspect = WGPUTextureAspect_All,
};
WGPUTexelCopyBufferLayout data_layout = {
.offset = 0,
.bytesPerRow = bytes_per_row,
.rowsPerImage = height,
};
WGPUExtent3D extent = { width, height, 1 };
wgpuQueueWriteTexture(queue, &info, data, data_layout.bytesPerRow * data_layout.rowsPerImage, &data_layout, &extent);
}
void renderer_texture_destroy(R_Texture texture) {
wgpuTextureViewRelease(texture->view);
wgpuTextureRelease(texture->texture);
SDL_free(texture);
}
Uint32 renderer_texture_get_width(R_Texture texture) {
return texture->width;
}
Uint32 renderer_texture_get_height(R_Texture texture) {
return texture->height;
}
bool renderer_texture_get_multisampled(R_Texture texture) {
return texture->multisampled;
}
R_Texture_Format renderer_texture_get_format(R_Texture texture) {
return texture->format;
}
R_Buffer renderer_buffer_create(R_Buffer_Usage usage, Uint32 num_bytes, void *data, const char *debug_name) {
WGPUBufferDescriptor descriptor = {
.label = { .data = debug_name, .length = WGPU_STRLEN },
.usage = buffer_usages[usage] | WGPUBufferUsage_CopyDst,
.size = num_bytes,
.mappedAtCreation = data != NULL,
};
WGPUBuffer buffer = wgpuDeviceCreateBuffer(device, &descriptor);
if (!buffer) return NULL;
if (data) {
void *mapped_data = wgpuBufferGetMappedRange(buffer, 0, num_bytes);
memcpy(mapped_data, data, num_bytes);
wgpuBufferUnmap(buffer);
}
R_Buffer_Impl *result = SDL_calloc(1, sizeof(R_Buffer_Impl));
if (!result) {
wgpuBufferRelease(buffer);
return NULL;
}
*result = (R_Buffer_Impl){
.buffer = buffer,
.num_bytes = num_bytes,
.usage = usage,
};
return result;
}
void renderer_buffer_update(R_Buffer buffer, Uint32 offset, Uint32 num_bytes, void *data) {
wgpuQueueWriteBuffer(queue, buffer->buffer, offset, data, num_bytes);
}
void renderer_buffer_destroy(R_Buffer buffer) {
wgpuBufferRelease(buffer->buffer);
SDL_free(buffer);
}
Uint32 renderer_buffer_get_size(R_Buffer buffer) {
return buffer->num_bytes;
}
R_Buffer_Usage renderer_buffer_get_usage(R_Buffer buffer){
return buffer->usage;
}
void renderer_frame_begin() {
SDL_assert(command_encoder == NULL);
SDL_assert(R_surface == NULL);
Sint32 window_width = 0, window_height = 0;
SDL_GetWindowSizeInPixels(window, &window_width, &window_height);
if (surface_configuration.width != window_width || surface_configuration.height != window_height) {
surface_configuration.width = window_width;
surface_configuration.height = window_height;
wgpuSurfaceConfigure(surface, &surface_configuration);
}
WGPUSurfaceTexture wgpu_surface_texture;
wgpuSurfaceGetCurrentTexture(surface, &wgpu_surface_texture);
if (wgpu_surface_texture.status != WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal && wgpu_surface_texture.status != WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to get current surface texture (%x). Exiting.", wgpu_surface_texture.status);
SDL_assert_always(false); // TODO: recovery on outdated or device loss
return;
}
surface_texture.texture = wgpu_surface_texture.texture;
surface_texture.view = wgpuTextureCreateView(wgpu_surface_texture.texture, NULL);
surface_texture.width = surface_configuration.width;
surface_texture.height = surface_configuration.height;
command_encoder = wgpuDeviceCreateCommandEncoder(device, NULL);
R_surface = &surface_texture;
}
void renderer_frame_end() {
SDL_assert(command_encoder);
SDL_assert(R_surface);
if (pass_encoder) {
wgpuRenderPassEncoderEnd(pass_encoder);
wgpuRenderPassEncoderRelease(pass_encoder);
pass_encoder = NULL;
}
wgpuTextureViewRelease(surface_texture.view);
surface_texture.view = NULL;
WGPUCommandBuffer command_buffer = wgpuCommandEncoderFinish(command_encoder, NULL);
wgpuCommandEncoderRelease(command_encoder);
command_encoder = NULL;
wgpuQueueSubmit(queue, 1, &command_buffer);
wgpuCommandBufferRelease(command_buffer);
wgpuSurfacePresent(surface);
wgpuTextureRelease(surface_texture.texture);
surface_texture.texture = NULL;
R_surface = NULL;
}
void renderer_frame_set_target(R_Texture target, R_Texture resolve_target, R_Load_Op load_op, R_Store_Op store_op, R_Color clear_color) {
SDL_assert(command_encoder);
if (pass_encoder) {
wgpuRenderPassEncoderEnd(pass_encoder);
wgpuRenderPassEncoderRelease(pass_encoder);
}
pass_encoder = wgpuCommandEncoderBeginRenderPass(command_encoder, &(WGPURenderPassDescriptor){
.colorAttachmentCount = 1,
.colorAttachments = &(WGPURenderPassColorAttachment) {
.view = target->view,
.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED,
.resolveTarget = resolve_target ? resolve_target->view : NULL,
.loadOp = load_ops[load_op],
.storeOp = store_ops[store_op],
.clearValue = { .r = clear_color.r, .g = clear_color.g, .b = clear_color.b, .a = clear_color.a },
},
});
}
void renderer_frame_set_shader(R_Shader shader) {
wgpuRenderPassEncoderSetPipeline(pass_encoder, shaders[shader]);
current_shader = shader;
}
void renderer_frame_draw(R_Buffer vertex_buffer, R_Buffer index_buffer, R_Buffer instance_buffer, Uint32 num_indices, Uint32 num_instances, const R_Draw_Resources *resources) {
SDL_assert(pass_encoder);
if (resources->num_vertex_sampled_textures || resources->num_vertex_storage_textures || resources->num_vertex_storage_buffers) {
WGPUBindGroupEntry *entries = SDL_stack_alloc(WGPUBindGroupEntry, resources->num_vertex_sampled_textures + resources->num_vertex_storage_textures + resources->num_vertex_storage_buffers);
for (int i = 0; i < resources->num_vertex_sampled_textures; i++) {
entries[i] = (WGPUBindGroupEntry){
.binding = i,
.textureView = resources->vertex_sampled_textures[i]->view,
};
}
Uint32 entry_offset = resources->num_vertex_sampled_textures;
for (int i = 0; i < resources->num_vertex_storage_textures; i++) {
entries[i] = (WGPUBindGroupEntry){
.binding = i,
.textureView = resources->vertex_storage_textures[i]->view,
};
}
entry_offset += resources->num_vertex_storage_textures;
for (int i = 0; i < resources->num_vertex_storage_buffers; i++) {
entries[entry_offset + i] = (WGPUBindGroupEntry){
.binding = entry_offset + i,
.buffer = resources->vertex_storage_buffers[i]->buffer,
.offset = 0,
.size = WGPU_WHOLE_SIZE,
};
}
WGPUBindGroup bind_group = wgpuDeviceCreateBindGroup(device, &(WGPUBindGroupDescriptor){
.layout = wgpuRenderPipelineGetBindGroupLayout(shaders[current_shader], 0),
.entryCount = resources->num_vertex_sampled_textures + resources->num_vertex_storage_textures + resources->num_vertex_storage_buffers,
.entries = entries,
});
SDL_stack_free(entries);
wgpuRenderPassEncoderSetBindGroup(pass_encoder, 0, bind_group, 0, NULL);
wgpuBindGroupRelease(bind_group);
} else {
wgpuRenderPassEncoderSetBindGroup(pass_encoder, 0, NULL, 0, NULL);
}
if (resources->num_vertex_uniform_buffers) {
WGPUBindGroupEntry *entries = SDL_stack_alloc(WGPUBindGroupEntry, resources->num_vertex_uniform_buffers);
for (int i = 0; i < resources->num_vertex_uniform_buffers; i++) {
entries[i] = (WGPUBindGroupEntry){
.binding = i,
.buffer = resources->vertex_uniform_buffers[i]->buffer,
.offset = 0,
.size = WGPU_WHOLE_SIZE,
};
}
WGPUBindGroup bind_group = wgpuDeviceCreateBindGroup(device, &(WGPUBindGroupDescriptor){
.layout = wgpuRenderPipelineGetBindGroupLayout(shaders[current_shader], 1),
.entryCount = resources->num_vertex_uniform_buffers,
.entries = entries,
});
SDL_stack_free(entries);
wgpuRenderPassEncoderSetBindGroup(pass_encoder, 1, bind_group, 0, NULL);
wgpuBindGroupRelease(bind_group);
} else {
wgpuRenderPassEncoderSetBindGroup(pass_encoder, 1, NULL, 0, NULL);
}
if (resources->num_fragment_sampled_textures || resources->num_fragment_storage_textures || resources->num_fragment_storage_buffers) {
WGPUBindGroupEntry *entries = SDL_stack_alloc(WGPUBindGroupEntry, resources->num_fragment_sampled_textures * 2 + resources->num_fragment_storage_textures + resources->num_fragment_storage_buffers);
for (int i = 0; i < resources->num_fragment_sampled_textures; i++) {
entries[i * 2] = (WGPUBindGroupEntry){
.binding = i * 2,
.textureView = resources->fragment_sampled_textures[i]->view,
};
entries[i * 2 + 1] = (WGPUBindGroupEntry){
.binding = i * 2 + 1,
.sampler = bilinear_sampler,
};
}
Uint32 entry_offset = resources->num_fragment_sampled_textures * 2;
for (int i = 0; i < resources->num_fragment_storage_textures; i++) {
entries[entry_offset + i] = (WGPUBindGroupEntry){
.binding = entry_offset + i,
.textureView = resources->fragment_storage_textures[i]->view,
};
}
entry_offset += resources->num_fragment_storage_textures;
for (int i = 0; i < resources->num_fragment_storage_buffers; i++) {
entries[entry_offset + i] = (WGPUBindGroupEntry){
.binding = entry_offset + i,
.buffer = resources->fragment_storage_buffers[i]->buffer,
.offset = 0,
.size = WGPU_WHOLE_SIZE,
};
}
WGPUBindGroup bind_group = wgpuDeviceCreateBindGroup(device, &(WGPUBindGroupDescriptor){
.layout = wgpuRenderPipelineGetBindGroupLayout(shaders[current_shader], 2),
.entryCount = resources->num_fragment_sampled_textures * 2 + resources->num_fragment_storage_textures + resources->num_fragment_storage_buffers,
.entries = entries,
});
SDL_stack_free(entries);
wgpuRenderPassEncoderSetBindGroup(pass_encoder, 2, bind_group, 0, NULL);
wgpuBindGroupRelease(bind_group);
} else {
wgpuRenderPassEncoderSetBindGroup(pass_encoder, 2, NULL, 0, NULL);
}
if (resources->num_fragment_uniform_buffers) {
WGPUBindGroupEntry *entries = SDL_stack_alloc(WGPUBindGroupEntry, resources->num_fragment_uniform_buffers);
for (int i = 0; i < resources->num_fragment_uniform_buffers; i++) {
entries[i] = (WGPUBindGroupEntry){
.binding = i,
.buffer = resources->fragment_uniform_buffers[i]->buffer,
.offset = 0,
.size = WGPU_WHOLE_SIZE,
};
}
WGPUBindGroup bind_group = wgpuDeviceCreateBindGroup(device, &(WGPUBindGroupDescriptor){
.layout = wgpuRenderPipelineGetBindGroupLayout(shaders[current_shader], 3),
.entryCount = resources->num_fragment_uniform_buffers,
.entries = entries,
});
SDL_stack_free(entries);
wgpuRenderPassEncoderSetBindGroup(pass_encoder, 3, bind_group, 0, NULL);
wgpuBindGroupRelease(bind_group);
} else {
wgpuRenderPassEncoderSetBindGroup(pass_encoder, 3, NULL, 0, NULL);
}
if (vertex_buffer) wgpuRenderPassEncoderSetVertexBuffer(pass_encoder, 0, vertex_buffer->buffer, 0, WGPU_WHOLE_SIZE);
if (instance_buffer) wgpuRenderPassEncoderSetVertexBuffer(pass_encoder, vertex_buffer ? 1 : 0, instance_buffer->buffer, 0, WGPU_WHOLE_SIZE);
if (index_buffer) {
wgpuRenderPassEncoderSetIndexBuffer(pass_encoder, index_buffer->buffer, WGPUIndexFormat_Uint16, 0, WGPU_WHOLE_SIZE);
wgpuRenderPassEncoderDrawIndexed(pass_encoder, num_indices, num_instances, 0, 0, 0);
} else {
wgpuRenderPassEncoderDraw(pass_encoder, num_indices, num_instances, 0, 0);
}
}
static WGPUSurface create_wgpu_surface_for_SDL_window(SDL_Window *window) {
SDL_PropertiesID properties = SDL_GetWindowProperties(window);
#if defined(SDL_PLATFORM_LINUX)
const char *display_system = SDL_GetCurrentVideoDriver();
if (SDL_strcmp(display_system, "wayland") == 0) {
void *display = SDL_GetPointerProperty(properties, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL);
void *surface = SDL_GetPointerProperty(properties, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL);
if (!display || !surface) return NULL;
WGPUSurfaceSourceWaylandSurface surface_source = {
.chain = { .next = NULL, .sType = WGPUSType_SurfaceSourceWaylandSurface },
.display = display,
.surface = surface,
};
WGPUSurfaceDescriptor descriptor = {
.nextInChain = &surface_source.chain,
.label = { .data = NULL, .length = WGPU_STRLEN },
};
return wgpuInstanceCreateSurface(instance, &descriptor);
} else if (SDL_strcmp(display_system, "x11") == 0) {
void *display = SDL_GetPointerProperty(properties, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL);
uint64_t xlib_window = SDL_GetNumberProperty (properties, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
if (!display || !xlib_window) return NULL;
WGPUSurfaceSourceXlibWindow surface_source = {
.chain = { .next = NULL, .sType = WGPUSType_SurfaceSourceXlibWindow },
.display = display,
.window = xlib_window,
};
WGPUSurfaceDescriptor descriptor = {
.nextInChain = &surface_source.chain,
.label = { .data = NULL, .length = WGPU_STRLEN },
};
return wgpuInstanceCreateSurface(instance, &descriptor);
} else {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "create_wgpu_surface_for_SDL_window is not implemented for this display system (%s).", display_system);
return NULL;
}
#elif defined(SDL_PLATFORM_WINDOWS)
void *hinstance = SDL_GetPointerProperty(properties, SDL_PROP_WINDOW_WIN32_INSTANCE_POINTER, NULL);
void *hwnd = SDL_GetPointerProperty(properties, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
if (!hinstance || !hwnd) return NULL;
WGPUSurfaceSourceWindowsHWND surface_source = {
.chain = { .next = NULL, .sType = WGPUSType_SurfaceSourceWindowsHWND },
.hinstance = hinstance,
.hwnd = hwnd,
};
WGPUSurfaceDescriptor descriptor = {
.nextInChain = &surface_source.chain,
.label = { .data = NULL, .length = WGPU_STRLEN },
};
return wgpuInstanceCreateSurface(instance, &descriptor);
#else
static_assert(false, "create_wgpu_surface_for_SDL_window is not implemented for this platform.");
#endif
return NULL;
}
static void device_request_callback(WGPURequestDeviceStatus status, WGPUDevice device_, WGPUStringView message, void *userdata1, void *userdata2) {
if (status != WGPURequestDeviceStatus_Success) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to request webgpu device.");
init_done = true;
return;
}
device = device_;
queue = wgpuDeviceGetQueue(device);
init_done = true;
}
static void adapter_request_callback(WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, void *userdata1, void *userdata2) {
if (status != WGPURequestAdapterStatus_Success) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to request webgpu adapter.");
init_done = true;
return;
}
WGPURequestDeviceCallbackInfo request_device_callback_info = {
.mode = WGPUCallbackMode_AllowProcessEvents,
.callback = device_request_callback,
};
WGPUDeviceDescriptor device_descriptor = {
.label = {},
.requiredFeatureCount = 0,
.requiredFeatures = NULL,
.requiredLimits = NULL,
.defaultQueue = {},
.deviceLostCallbackInfo = {},
.uncapturedErrorCallbackInfo = {},
};
wgpuAdapterRequestDevice(adapter, &device_descriptor, request_device_callback_info);
}
bool renderer_init(SDL_Window *window_) {
window = window_;
instance = wgpuCreateInstance(NULL);
if (!instance) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to create webgpu instance.");
return false;
}
surface = create_wgpu_surface_for_SDL_window(window);
if (!surface) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to create webgpu surface for SDL window.");
return false;
}
WGPURequestAdapterCallbackInfo request_adapter_callback_info = {
.mode = WGPUCallbackMode_AllowProcessEvents,
.callback = adapter_request_callback,
};
WGPURequestAdapterOptions request_adapter_options = {
.featureLevel = WGPUFeatureLevel_Core,
.powerPreference = WGPUPowerPreference_HighPerformance,
.forceFallbackAdapter = false,
.backendType = WGPUBackendType_Vulkan,
.compatibleSurface = surface,
};
wgpuInstanceRequestAdapter(instance, &request_adapter_options, request_adapter_callback_info);
while (!init_done) {
wgpuInstanceProcessEvents(instance);
}
if (!device) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to get webgpu device.");
return false;
}
surface_texture.format = R_TEXTURE_FORMAT_BGRA8_UNORM_SRGB;
surface_configuration = (WGPUSurfaceConfiguration){
.device = device,
.format = WGPUTextureFormat_BGRA8UnormSrgb,
.usage = WGPUTextureUsage_RenderAttachment,
.width = 1280,
.height = 720,
.viewFormatCount = 0,
.viewFormats = NULL,
.alphaMode = WGPUCompositeAlphaMode_Opaque,
.presentMode = WGPUPresentMode_Fifo,
};
wgpuSurfaceConfigure(surface, &surface_configuration);
WGPUSamplerDescriptor bilinear_sampler_descriptor = {
.label = { .data = "bilinear_sampler", .length = WGPU_STRLEN },
.addressModeU = WGPUAddressMode_ClampToEdge,
.addressModeV = WGPUAddressMode_ClampToEdge,
.addressModeW = WGPUAddressMode_ClampToEdge,
.magFilter = WGPUFilterMode_Linear,
.minFilter = WGPUFilterMode_Linear,
.mipmapFilter = WGPUMipmapFilterMode_Nearest,
.maxAnisotropy = 1,
};
bilinear_sampler = wgpuDeviceCreateSampler(device, &bilinear_sampler_descriptor);
WGPUBlendState blend_state = {
.color = { .operation = WGPUBlendOperation_Add, .srcFactor = WGPUBlendFactor_SrcAlpha, .dstFactor = WGPUBlendFactor_OneMinusSrcAlpha },
.alpha = { .operation = WGPUBlendOperation_Add, .srcFactor = WGPUBlendFactor_SrcAlpha, .dstFactor = WGPUBlendFactor_OneMinusSrcAlpha },
};
WGPUColorTargetState color_target_state = {
.format = surface_configuration.format,
.blend = &blend_state,
.writeMask = WGPUColorWriteMask_All,
};
// Shaders
{ // Character
const char shader_source[] = {
#embed "shaders/wgsl/character.wgsl"
};
WGPUShaderModule shader = wgpuDeviceCreateShaderModule(device, &(WGPUShaderModuleDescriptor){
.nextInChain = (WGPUChainedStruct *)&(WGPUShaderSourceWGSL){
.chain = { .next = NULL, .sType = WGPUSType_ShaderSourceWGSL },
.code = { .data = shader_source, .length = SDL_arraysize(shader_source) },
},
.label = { .data = "character shader", .length = WGPU_STRLEN }
});
shaders[R_SHADER_CHARACTER] = wgpuDeviceCreateRenderPipeline(device, &(WGPURenderPipelineDescriptor){
.label = { .data = "character render_pipeline", .length = WGPU_STRLEN },
.layout = wgpuDeviceCreatePipelineLayout(device, &(WGPUPipelineLayoutDescriptor){
.label = { .data = "character pipeline_layout", .length = WGPU_STRLEN },
.bindGroupLayoutCount = 4,
.bindGroupLayouts = (WGPUBindGroupLayout[]){
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 0,
.entries = (WGPUBindGroupLayoutEntry[]){
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 1,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Vertex, .buffer = { .type = WGPUBufferBindingType_Uniform } },
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 2,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Fragment, .texture = { .sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D } },
{ .binding = 1, .visibility = WGPUShaderStage_Fragment, .sampler = { .type = WGPUSamplerBindingType_Filtering } },
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 1,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Fragment, .buffer = { .type = WGPUBufferBindingType_Uniform } },
},
}),
},
}),
.vertex = {
.module = shader,
.entryPoint = { .data = "main_vertex", .length = WGPU_STRLEN },
.constantCount = 0,
.constants = NULL,
.bufferCount = 1,
.buffers = (WGPUVertexBufferLayout[]){
{
.stepMode = WGPUVertexStepMode_Instance,
.arrayStride = 12,
.attributeCount = 2,
.attributes = (WGPUVertexAttribute[]){
{
.format = WGPUVertexFormat_Float32x2,
.offset = 0,
.shaderLocation = 2,
},
{
.format = WGPUVertexFormat_Uint32,
.offset = 8,
.shaderLocation = 3,
},
},
},
},
},
.primitive = {
.topology = WGPUPrimitiveTopology_TriangleList,
.stripIndexFormat = WGPUIndexFormat_Undefined,
.frontFace = WGPUFrontFace_CCW,
.cullMode = WGPUCullMode_Back,
.unclippedDepth = false,
},
.depthStencil = NULL,
.multisample = {
.count = 4,
.mask = ~0u,
.alphaToCoverageEnabled = false,
},
.fragment = &(WGPUFragmentState){
.module = shader,
.entryPoint = { .data = "main_fragment", .length = WGPU_STRLEN },
.constantCount = 0,
.constants = NULL,
.targetCount = 1,
.targets = &color_target_state,
},
});
wgpuShaderModuleRelease(shader);
}
{ // Character Shadow
const char shader_source[] = {
#embed "shaders/wgsl/character_shadow.wgsl"
};
WGPUShaderModule shader = wgpuDeviceCreateShaderModule(device, &(WGPUShaderModuleDescriptor){
.nextInChain = (WGPUChainedStruct *)&(WGPUShaderSourceWGSL){
.chain = { .next = NULL, .sType = WGPUSType_ShaderSourceWGSL },
.code = { .data = shader_source, .length = SDL_arraysize(shader_source) },
},
.label = { .data = "character_shadow shader", .length = WGPU_STRLEN }
});
shaders[R_SHADER_CHARACTER_SHADOW] = wgpuDeviceCreateRenderPipeline(device, &(WGPURenderPipelineDescriptor){
.label = { .data = "character_shadow render_pipeline", .length = WGPU_STRLEN },
.layout = wgpuDeviceCreatePipelineLayout(device, &(WGPUPipelineLayoutDescriptor){
.label = { .data = "character_shadow pipeline_layout", .length = WGPU_STRLEN },
.bindGroupLayoutCount = 4,
.bindGroupLayouts = (WGPUBindGroupLayout[]){
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 0,
.entries = (WGPUBindGroupLayoutEntry[]){
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 1,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Vertex, .buffer = { .type = WGPUBufferBindingType_Uniform } },
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 2,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Fragment, .texture = { .sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D } },
{ .binding = 1, .visibility = WGPUShaderStage_Fragment, .sampler = { .type = WGPUSamplerBindingType_Filtering } },
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 1,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Fragment, .buffer = { .type = WGPUBufferBindingType_Uniform } },
},
}),
},
}),
.vertex = {
.module = shader,
.entryPoint = { .data = "main_vertex", .length = WGPU_STRLEN },
.constantCount = 0,
.constants = NULL,
.bufferCount = 1,
.buffers = (WGPUVertexBufferLayout[]){
{
.stepMode = WGPUVertexStepMode_Instance,
.arrayStride = 12,
.attributeCount = 2,
.attributes = (WGPUVertexAttribute[]){
{
.format = WGPUVertexFormat_Float32x2,
.offset = 0,
.shaderLocation = 2,
},
{
.format = WGPUVertexFormat_Uint32,
.offset = 8,
.shaderLocation = 3,
},
},
},
},
},
.primitive = {
.topology = WGPUPrimitiveTopology_TriangleList,
.stripIndexFormat = WGPUIndexFormat_Undefined,
.frontFace = WGPUFrontFace_CCW,
.cullMode = WGPUCullMode_Back,
.unclippedDepth = false,
},
.depthStencil = NULL,
.multisample = {
.count = 4,
.mask = ~0u,
.alphaToCoverageEnabled = false,
},
.fragment = &(WGPUFragmentState){
.module = shader,
.entryPoint = { .data = "main_fragment", .length = WGPU_STRLEN },
.constantCount = 0,
.constants = NULL,
.targetCount = 1,
.targets = &color_target_state,
},
});
wgpuShaderModuleRelease(shader);
}
{ // World
const char shader_source[] = {
#embed "shaders/wgsl/world.wgsl"
};
WGPUShaderModule shader = wgpuDeviceCreateShaderModule(device, &(WGPUShaderModuleDescriptor){
.nextInChain = (WGPUChainedStruct *)&(WGPUShaderSourceWGSL){
.chain = { .next = NULL, .sType = WGPUSType_ShaderSourceWGSL },
.code = { .data = shader_source, .length = SDL_arraysize(shader_source) },
},
.label = { .data = "world shader", .length = WGPU_STRLEN }
});
shaders[R_SHADER_WORLD] = wgpuDeviceCreateRenderPipeline(device, &(WGPURenderPipelineDescriptor){
.label = { .data = "world render_pipeline", .length = WGPU_STRLEN },
.layout = wgpuDeviceCreatePipelineLayout(device, &(WGPUPipelineLayoutDescriptor){
.label = { .data = "world pipeline_layout", .length = WGPU_STRLEN },
.bindGroupLayoutCount = 4,
.bindGroupLayouts = (WGPUBindGroupLayout[]){
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 1,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Vertex, .texture = { .sampleType = WGPUTextureSampleType_Uint, .viewDimension = WGPUTextureViewDimension_2D } },
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 2,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Vertex, .buffer = { .type = WGPUBufferBindingType_Uniform } },
{ .binding = 1, .visibility = WGPUShaderStage_Vertex, .buffer = { .type = WGPUBufferBindingType_Uniform } },
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 3,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Fragment, .texture = { .sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D } },
{ .binding = 1, .visibility = WGPUShaderStage_Fragment, .sampler = { .type = WGPUSamplerBindingType_Filtering } },
{ .binding = 2, .visibility = WGPUShaderStage_Fragment, .buffer = { .type = WGPUBufferBindingType_ReadOnlyStorage } },
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 1,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Fragment, .buffer = { .type = WGPUBufferBindingType_Uniform } },
},
}),
},
}),
.vertex = {
.module = shader,
.entryPoint = { .data = "main_vertex", .length = WGPU_STRLEN },
.constantCount = 0,
.constants = NULL,
.bufferCount = 0,
.buffers = (WGPUVertexBufferLayout[]){
},
},
.primitive = {
.topology = WGPUPrimitiveTopology_TriangleList,
.stripIndexFormat = WGPUIndexFormat_Undefined,
.frontFace = WGPUFrontFace_CCW,
.cullMode = WGPUCullMode_Back,
.unclippedDepth = false,
},
.depthStencil = NULL,
.multisample = {
.count = 4,
.mask = ~0u,
.alphaToCoverageEnabled = false,
},
.fragment = &(WGPUFragmentState){
.module = shader,
.entryPoint = { .data = "main_fragment", .length = WGPU_STRLEN },
.constantCount = 0,
.constants = NULL,
.targetCount = 1,
.targets = &color_target_state,
},
});
wgpuShaderModuleRelease(shader);
}
{ // Grid
const char shader_source[] = {
#embed "shaders/wgsl/grid.wgsl"
};
WGPUShaderModule shader = wgpuDeviceCreateShaderModule(device, &(WGPUShaderModuleDescriptor){
.nextInChain = (WGPUChainedStruct *)&(WGPUShaderSourceWGSL){
.chain = { .next = NULL, .sType = WGPUSType_ShaderSourceWGSL },
.code = { .data = shader_source, .length = SDL_arraysize(shader_source) },
},
.label = { .data = "grid shader", .length = WGPU_STRLEN }
});
shaders[R_SHADER_GRID] = wgpuDeviceCreateRenderPipeline(device, &(WGPURenderPipelineDescriptor){
.label = { .data = "grid render_pipeline", .length = WGPU_STRLEN },
.layout = wgpuDeviceCreatePipelineLayout(device, &(WGPUPipelineLayoutDescriptor){
.label = { .data = "grid pipeline_layout", .length = WGPU_STRLEN },
.bindGroupLayoutCount = 4,
.bindGroupLayouts = (WGPUBindGroupLayout[]){
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 0,
.entries = (WGPUBindGroupLayoutEntry[]){
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 2,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Vertex, .buffer = { .type = WGPUBufferBindingType_Uniform } },
{ .binding = 1, .visibility = WGPUShaderStage_Vertex, .buffer = { .type = WGPUBufferBindingType_Uniform } },
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 0,
.entries = (WGPUBindGroupLayoutEntry[]){
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 0,
.entries = (WGPUBindGroupLayoutEntry[]){
},
}),
},
}),
.vertex = {
.module = shader,
.entryPoint = { .data = "main_vertex", .length = WGPU_STRLEN },
.constantCount = 0,
.constants = NULL,
.bufferCount = 0,
.buffers = NULL,
},
.primitive = {
.topology = WGPUPrimitiveTopology_LineList,
.stripIndexFormat = WGPUIndexFormat_Undefined,
.frontFace = WGPUFrontFace_CCW,
.cullMode = WGPUCullMode_Back,
.unclippedDepth = false,
},
.depthStencil = NULL,
.multisample = {
.count = 4,
.mask = ~0u,
.alphaToCoverageEnabled = false,
},
.fragment = &(WGPUFragmentState){
.module = shader,
.entryPoint = { .data = "main_fragment", .length = WGPU_STRLEN },
.constantCount = 0,
.constants = NULL,
.targetCount = 1,
.targets = &color_target_state,
},
});
wgpuShaderModuleRelease(shader);
}
for (int i = 0; i < R_SHADER_COUNT; i++) {
SDL_assert(shaders[i]);
}
return true;
}
void ImGui_ImplRenderer_Init() {
cImGui_ImplWGPU_Init(&(ImGui_ImplWGPU_InitInfo){
.Device = device,
.NumFramesInFlight = 3,
.RenderTargetFormat = surface_configuration.format,
.PipelineMultisampleState = {
.count = 4,
.mask = ~0,
.alphaToCoverageEnabled = false,
},
});
}
void ImGui_ImplRenderer_NewFrame() {
cImGui_ImplWGPU_NewFrame();
}
void ImGui_ImplRenderer_Shutdown() {
cImGui_ImplWGPU_Shutdown();
}
void ImGui_ImplRenderer_RenderDrawData(void* draw_data) {
cImGui_ImplWGPU_RenderDrawData(draw_data, pass_encoder);
}
Uint64 ImGui_ImplRenderer_GetTextureID(R_Texture texture) {
return (Uint64)texture->view;
}