diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e7a037976..047494824 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -25,6 +25,18 @@ set(GETOPT "${GLFW_SOURCE_DIR}/deps/getopt.h" set(TINYCTHREAD "${GLFW_SOURCE_DIR}/deps/tinycthread.h" "${GLFW_SOURCE_DIR}/deps/tinycthread.c") +include(FetchContent) + +FetchContent_Declare( + webgpu + GIT_REPOSITORY https://github.com/eliemichel/WebGPU-distribution.git + GIT_TAG v0.3.0-gamma + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + EXCLUDE_FROM_ALL +) +FetchContent_MakeAvailable(webgpu) + add_executable(boing WIN32 MACOSX_BUNDLE boing.c ${ICON} ${GLAD_GL}) add_executable(gears WIN32 MACOSX_BUNDLE gears.c ${ICON} ${GLAD_GL}) add_executable(heightmap WIN32 MACOSX_BUNDLE heightmap.c ${ICON} ${GLAD_GL}) @@ -34,6 +46,7 @@ add_executable(sharing WIN32 MACOSX_BUNDLE sharing.c ${ICON} ${GLAD_GL}) add_executable(splitview WIN32 MACOSX_BUNDLE splitview.c ${ICON} ${GLAD_GL}) add_executable(triangle-opengl WIN32 MACOSX_BUNDLE triangle-opengl.c ${ICON} ${GLAD_GL}) add_executable(triangle-opengles WIN32 MACOSX_BUNDLE triangle-opengles.c ${ICON} ${GLAD_GLES2}) +add_executable(triangle-webgpu WIN32 MACOSX_BUNDLE triangle-webgpu.c ${ICON}) add_executable(wave WIN32 MACOSX_BUNDLE wave.c ${ICON} ${GLAD_GL}) add_executable(windows WIN32 MACOSX_BUNDLE windows.c ${ICON} ${GLAD_GL}) @@ -42,8 +55,11 @@ if (RT_LIBRARY) target_link_libraries(particles "${RT_LIBRARY}") endif() +target_link_libraries(triangle-webgpu webgpu) +target_copy_webgpu_binaries(triangle-webgpu) + set(GUI_ONLY_BINARIES boing gears heightmap particles sharing splitview - triangle-opengl triangle-opengles wave windows) + triangle-opengl triangle-opengles triangle-webgpu wave windows) set(CONSOLE_BINARIES offscreen) set_target_properties(${GUI_ONLY_BINARIES} ${CONSOLE_BINARIES} PROPERTIES @@ -68,6 +84,7 @@ if (APPLE) set_target_properties(sharing PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Sharing") set_target_properties(triangle-opengl PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "OpenGL Triangle") set_target_properties(triangle-opengles PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "OpenGL ES Triangle") + set_target_properties(triangle-webgpu PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "WebGPU Triangle") set_target_properties(splitview PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "SplitView") set_target_properties(wave PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Wave") set_target_properties(windows PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Windows") diff --git a/examples/triangle-webgpu.c b/examples/triangle-webgpu.c new file mode 100644 index 000000000..e94efd652 --- /dev/null +++ b/examples/triangle-webgpu.c @@ -0,0 +1,463 @@ +//======================================================================== +// WebGPU triangle example +// Copyright (c) Sebastian Dawid +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== +//! [code] + +#include +#include +#include +#include + +#include +#include +#define GLFW_INCLUDE_NONE +#include + +#define SHADER_SOURCE(...) #__VA_ARGS__ + +static const char* CODE = SHADER_SOURCE( +@vertex +fn vert(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f { + var p = vec2f(0.0, 0.0); + + if (index == 0u) { + p = vec2f(-0.5, -0.5); + } else if (index == 1u) { + p = vec2f(0.5, -0.5); + } else { + p = vec2f(0.0, 0.5); + } + + return vec4f(p, 0.0, 1.0); +} + +@fragment +fn frag() -> @location(0) vec4f { + return vec4f(1.0, 0.0, 0.0, 1.0); +} +); + +static void error_callback(int error, const char* description); +static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods); + +static WGPULimits get_required_limits(WGPULimits* supported_limits); +static void adapter_callback(WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, void* userdata1, void* userdata2); +static void device_callback(WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView message, void* userdata1, void* userdata2); +static void device_lost_callback(const WGPUDevice* device, WGPUDeviceLostReason reason, WGPUStringView message, void* userdata1, void* userdata2); +static void uncaptured_error_callback(const WGPUDevice* device, WGPUErrorType error, WGPUStringView message, void* userdata1, void* userdata2); + +int main(void) +{ + glfwSetErrorCallback(error_callback); + if (!glfwInit()) + exit(EXIT_FAILURE); + glfwSetWGPUInstanceCreateSurfaceAddr(wgpuInstanceCreateSurface); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + GLFWwindow* window = glfwCreateWindow(800, 600, "WebGPU Triangle", NULL, NULL); + if (!window) + { + glfwTerminate(); + exit(EXIT_FAILURE); + } + + glfwSetKeyCallback(window, key_callback); + + WGPUInstance instance = wgpuCreateInstance(NULL); + if (!instance) + { + fprintf(stderr, "Error: Failed to create WebGPU instance.\n"); + glfwDestroyWindow(window); + glfwTerminate(); + exit(EXIT_FAILURE); + } + + WGPUSurface surface = glfwCreateWindowWGPUSurface(instance, window); + if (!surface) + { + fprintf(stderr, "Error: Failed to create WebGPU surface.\n"); + wgpuInstanceRelease(instance); + glfwDestroyWindow(window); + glfwTerminate(); + exit(EXIT_FAILURE); + } + WGPUAdapter adapter; + WGPUDevice device; + WGPUQueue queue; + bool success = false; + + WGPURequestAdapterOptions adapter_options = {0}; + adapter_options.nextInChain = NULL; + adapter_options.compatibleSurface = surface; + + WGPURequestAdapterCallbackInfo adapter_callback_info = {0}; + adapter_callback_info.nextInChain = NULL; + adapter_callback_info.mode = WGPUCallbackMode_AllowSpontaneous; + adapter_callback_info.callback = adapter_callback; + adapter_callback_info.userdata1 = &adapter; + adapter_callback_info.userdata2 = &success; + + wgpuInstanceRequestAdapter(instance, &adapter_options, adapter_callback_info); + + while (!success); // spin until we get the adapter + success = false; + + WGPULimits supported_limits = {0}; + wgpuAdapterGetLimits(adapter, &supported_limits); + WGPULimits required_limits = get_required_limits(&supported_limits); + + WGPUDeviceDescriptor device_descriptor = {0}; + device_descriptor.nextInChain = NULL; + device_descriptor.label = (WGPUStringView) { "device", 6 }; + device_descriptor.requiredFeatureCount = 0; + device_descriptor.requiredFeatures = NULL; + device_descriptor.requiredLimits = &required_limits; + + device_descriptor.defaultQueue.nextInChain = NULL; + device_descriptor.defaultQueue.label = (WGPUStringView) { "queue", 5 }; + + device_descriptor.deviceLostCallbackInfo.nextInChain = NULL; + device_descriptor.deviceLostCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous; + device_descriptor.deviceLostCallbackInfo.callback = device_lost_callback; + device_descriptor.deviceLostCallbackInfo.userdata1 = NULL; + device_descriptor.deviceLostCallbackInfo.userdata2 = NULL; + + WGPURequestDeviceCallbackInfo device_callback_info = {0}; + device_callback_info.nextInChain = NULL; + device_callback_info.mode = WGPUCallbackMode_AllowSpontaneous; + device_callback_info.callback = device_callback; + device_callback_info.userdata1 = &device; + device_callback_info.userdata2 = &success; + + wgpuAdapterRequestDevice(adapter, &device_descriptor, device_callback_info); + + while (!success); // spin until we get the dvice + success = false; + + queue = wgpuDeviceGetQueue(device); + + WGPUSurfaceCapabilities surface_capabilities = {0}; + surface_capabilities.nextInChain = NULL; + if (wgpuSurfaceGetCapabilities(surface, adapter, &surface_capabilities) != WGPUStatus_Success) + { + fprintf(stderr, "Error: Failed to get surface capabilities.\n"); + wgpuQueueRelease(queue); + wgpuDeviceRelease(device); + wgpuSurfaceRelease(surface); + glfwDestroyWindow(window); + glfwTerminate(); + exit(EXIT_FAILURE); + } + + WGPUTextureFormat format = {0}; + for (size_t i = 0; i < surface_capabilities.formatCount; ++i) + { + WGPUTextureFormat f = surface_capabilities.formats[i]; + if (f == WGPUTextureFormat_BGRA8UnormSrgb || f == WGPUTextureFormat_RGBA8UnormSrgb) + { + format = f; + break; + } + } + + WGPUSurfaceConfiguration surface_configuration = {0}; + surface_configuration.nextInChain = NULL; + surface_configuration.width = 800; + surface_configuration.height = 600; + surface_configuration.format = format; + surface_configuration.viewFormatCount = 0; + surface_configuration.viewFormats = NULL; + surface_configuration.usage = WGPUTextureUsage_RenderAttachment; + surface_configuration.presentMode = WGPUPresentMode_Fifo; + surface_configuration.alphaMode = WGPUCompositeAlphaMode_Auto; + surface_configuration.device = device; + + wgpuSurfaceConfigure(surface, &surface_configuration); + + wgpuAdapterRelease(adapter); + wgpuInstanceRelease(instance); + + WGPUShaderSourceWGSL shader_source = {0}; + shader_source.chain.next = NULL; + shader_source.chain.sType = WGPUSType_ShaderSourceWGSL; + shader_source.code = (WGPUStringView) { CODE, strlen(CODE) }; + + WGPUShaderModuleDescriptor module_descriptor = {0}; + module_descriptor.nextInChain = &shader_source.chain; + module_descriptor.label = (WGPUStringView) { NULL, SIZE_MAX }; + + WGPUShaderModule module = wgpuDeviceCreateShaderModule(device, &module_descriptor); + + WGPURenderPipelineDescriptor pipeline_descriptor = {0}; + pipeline_descriptor.nextInChain = NULL; + pipeline_descriptor.label = (WGPUStringView) { NULL, SIZE_MAX }; + pipeline_descriptor.layout = NULL; + + pipeline_descriptor.vertex.nextInChain = NULL; + pipeline_descriptor.vertex.entryPoint = (WGPUStringView) { "vert", 4 }; + pipeline_descriptor.vertex.module = module; + pipeline_descriptor.vertex.bufferCount = 0; + pipeline_descriptor.vertex.buffers = NULL; + pipeline_descriptor.vertex.constantCount = 0; + pipeline_descriptor.vertex.constants = NULL; + + pipeline_descriptor.primitive.nextInChain = NULL; + pipeline_descriptor.primitive.topology = WGPUPrimitiveTopology_TriangleList; + pipeline_descriptor.primitive.stripIndexFormat = WGPUIndexFormat_Undefined; + pipeline_descriptor.primitive.frontFace = WGPUFrontFace_CCW; + pipeline_descriptor.primitive.cullMode = WGPUCullMode_None; + + WGPUBlendState blend_state = {0}; + blend_state.color.srcFactor = WGPUBlendFactor_SrcAlpha; + blend_state.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; + blend_state.color.operation = WGPUBlendOperation_Add; + + blend_state.alpha.srcFactor = WGPUBlendFactor_Zero; + blend_state.alpha.dstFactor = WGPUBlendFactor_One; + blend_state.alpha.operation = WGPUBlendOperation_Add; + + WGPUColorTargetState color_target_state = {0}; + color_target_state.nextInChain = NULL; + color_target_state.format = format; + color_target_state.blend = &blend_state; + color_target_state.writeMask = WGPUColorWriteMask_All; + + WGPUFragmentState fragment_state = {0}; + fragment_state.nextInChain = NULL; + fragment_state.module = module; + fragment_state.entryPoint = (WGPUStringView) { "frag", 4 }; + fragment_state.constantCount = 0; + fragment_state.constants = NULL; + fragment_state.targetCount = 1; + fragment_state.targets = &color_target_state; + + pipeline_descriptor.fragment = &fragment_state; + + pipeline_descriptor.multisample.count = 1; + pipeline_descriptor.multisample.mask = ~0u; + pipeline_descriptor.multisample.alphaToCoverageEnabled = false; + + pipeline_descriptor.depthStencil = NULL; + + WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(device, &pipeline_descriptor); + + wgpuShaderModuleRelease(module); + + WGPUSurfaceTexture surface_texture = {0}; + WGPUTextureView target_view = {0}; + while (!glfwWindowShouldClose(window)) + { + wgpuDevicePoll(device, false, NULL); + glfwPollEvents(); + + wgpuSurfaceGetCurrentTexture(surface, &surface_texture); + + if (surface_texture.status == WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal) + { + WGPUTextureViewDescriptor view_descriptor = {0}; + view_descriptor.nextInChain = NULL; + view_descriptor.label = (WGPUStringView) { NULL, SIZE_MAX }; + view_descriptor.format = wgpuTextureGetFormat(surface_texture.texture); + view_descriptor.dimension = WGPUTextureViewDimension_2D; + view_descriptor.baseMipLevel = 0; + view_descriptor.mipLevelCount = 1; + view_descriptor.baseArrayLayer = 0; + view_descriptor.arrayLayerCount = 1; + view_descriptor.aspect = WGPUTextureAspect_All; + view_descriptor.usage = WGPUTextureUsage_RenderAttachment; + + target_view = wgpuTextureCreateView(surface_texture.texture, &view_descriptor); + } + + if (target_view == NULL) + continue; + + WGPURenderPassDescriptor render_pass_descriptor = {0}; + render_pass_descriptor.nextInChain = NULL; + + WGPURenderPassColorAttachment color_attachment = {0}; + color_attachment.view = target_view; + color_attachment.resolveTarget = NULL; + color_attachment.loadOp = WGPULoadOp_Clear; + color_attachment.storeOp = WGPUStoreOp_Store; + color_attachment.clearValue = (WGPUColor) { 0.0f, 0.0f, 0.0f, 1.0f }; + render_pass_descriptor.colorAttachmentCount = 1; + render_pass_descriptor.colorAttachments = &color_attachment; + + render_pass_descriptor.depthStencilAttachment = NULL; + render_pass_descriptor.timestampWrites = NULL; + + WGPUCommandEncoderDescriptor encoder_descriptor = {0}; + encoder_descriptor.nextInChain = NULL; + encoder_descriptor.label = (WGPUStringView) { NULL, SIZE_MAX }; + WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, &encoder_descriptor); + + WGPURenderPassEncoder render_pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_descriptor); + + wgpuRenderPassEncoderSetPipeline(render_pass, pipeline); + wgpuRenderPassEncoderDraw(render_pass, 3, 1, 0, 0); + + wgpuRenderPassEncoderEnd(render_pass); + wgpuRenderPassEncoderRelease(render_pass); + + WGPUCommandBufferDescriptor command_buffer_descriptor = {0}; + command_buffer_descriptor.nextInChain = NULL; + command_buffer_descriptor.label = (WGPUStringView) { NULL, SIZE_MAX }; + + WGPUCommandBuffer command_buffer = wgpuCommandEncoderFinish(encoder, &command_buffer_descriptor); + + wgpuQueueSubmit(queue, 1, &command_buffer); + wgpuCommandBufferRelease(command_buffer); + + wgpuTextureViewRelease(target_view); + target_view = NULL; + wgpuSurfacePresent(surface); + } + + wgpuQueueRelease(queue); + wgpuDeviceRelease(device); + wgpuSurfaceRelease(surface); + glfwDestroyWindow(window); + glfwTerminate(); + exit(EXIT_SUCCESS); +} + +void error_callback(int error, const char* description) +{ + fprintf(stderr, "Error: %s\n", description); +} + +void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) + glfwSetWindowShouldClose(window, GLFW_TRUE); +} + +static const WGPULimits DEFAULT_LIMITS = { + .nextInChain = NULL, + .maxTextureDimension1D = WGPU_LIMIT_U32_UNDEFINED, + .maxTextureDimension2D = WGPU_LIMIT_U32_UNDEFINED, + .maxTextureDimension3D = WGPU_LIMIT_U32_UNDEFINED, + .maxTextureArrayLayers = WGPU_LIMIT_U32_UNDEFINED, + .maxBindGroups = WGPU_LIMIT_U32_UNDEFINED, + .maxBindGroupsPlusVertexBuffers = WGPU_LIMIT_U32_UNDEFINED, + .maxBindingsPerBindGroup = WGPU_LIMIT_U32_UNDEFINED, + .maxDynamicUniformBuffersPerPipelineLayout = WGPU_LIMIT_U32_UNDEFINED, + .maxDynamicStorageBuffersPerPipelineLayout = WGPU_LIMIT_U32_UNDEFINED, + .maxSampledTexturesPerShaderStage = WGPU_LIMIT_U32_UNDEFINED, + .maxSamplersPerShaderStage = WGPU_LIMIT_U32_UNDEFINED, + .maxStorageBuffersPerShaderStage = WGPU_LIMIT_U32_UNDEFINED, + .maxStorageTexturesPerShaderStage = WGPU_LIMIT_U32_UNDEFINED, + .maxUniformBuffersPerShaderStage = WGPU_LIMIT_U32_UNDEFINED, + .maxUniformBufferBindingSize = WGPU_LIMIT_U64_UNDEFINED, + .maxStorageBufferBindingSize = WGPU_LIMIT_U64_UNDEFINED, + .minUniformBufferOffsetAlignment = WGPU_LIMIT_U32_UNDEFINED, + .minStorageBufferOffsetAlignment = WGPU_LIMIT_U32_UNDEFINED, + .maxVertexBuffers = WGPU_LIMIT_U32_UNDEFINED, + .maxBufferSize = WGPU_LIMIT_U64_UNDEFINED, + .maxVertexAttributes = WGPU_LIMIT_U32_UNDEFINED, + .maxVertexBufferArrayStride = WGPU_LIMIT_U32_UNDEFINED, + .maxInterStageShaderVariables = WGPU_LIMIT_U32_UNDEFINED, + .maxColorAttachments = WGPU_LIMIT_U32_UNDEFINED, + .maxColorAttachmentBytesPerSample = WGPU_LIMIT_U32_UNDEFINED, + .maxComputeWorkgroupStorageSize = WGPU_LIMIT_U32_UNDEFINED, + .maxComputeInvocationsPerWorkgroup = WGPU_LIMIT_U32_UNDEFINED, + .maxComputeWorkgroupSizeX = WGPU_LIMIT_U32_UNDEFINED, + .maxComputeWorkgroupSizeY = WGPU_LIMIT_U32_UNDEFINED, + .maxComputeWorkgroupSizeZ = WGPU_LIMIT_U32_UNDEFINED, + .maxComputeWorkgroupsPerDimension = WGPU_LIMIT_U32_UNDEFINED +}; + +WGPULimits get_required_limits(WGPULimits* supported_limits) { + WGPULimits required_limits = DEFAULT_LIMITS; + + required_limits.maxVertexAttributes = 3; + required_limits.maxVertexBuffers = 1; + required_limits.maxBufferSize = 256 * 1024 * 1024; // 256 MiB + required_limits.maxVertexBufferArrayStride = sizeof(float) * 8; + required_limits.maxInterStageShaderVariables = 6; + + required_limits.maxBindGroups = 2; + required_limits.maxUniformBuffersPerShaderStage = 2; + required_limits.maxUniformBufferBindingSize = 4 * 16 * sizeof(float); + required_limits.maxSampledTexturesPerShaderStage = 1; + required_limits.maxSamplersPerShaderStage = 1; + + // require default maximum value for 2D textures + required_limits.maxTextureDimension1D = 8192; + required_limits.maxTextureDimension2D = 8192; + required_limits.maxTextureArrayLayers = 1; + + // explicitly forward minimum limits, since they may cause issues otherwise + required_limits.minStorageBufferOffsetAlignment = supported_limits->minStorageBufferOffsetAlignment; + required_limits.minUniformBufferOffsetAlignment = supported_limits->minUniformBufferOffsetAlignment; + + return required_limits; +} + +void adapter_callback( + WGPURequestAdapterStatus status, WGPUAdapter adapter, + WGPUStringView message, void* userdata1, void* userdata2 +) { + if (status == WGPURequestAdapterStatus_Success) { + *(WGPUAdapter*)userdata1 = adapter; + } else { + fprintf(stderr, "Failed to get WebGPU adapter: %s\n", message.data); + return; + } + *(bool*)userdata2 = true; +} + +void device_callback( + WGPURequestDeviceStatus status, WGPUDevice device, + WGPUStringView message, void* userdata1, void* userdata2 +) { + if (status == WGPURequestDeviceStatus_Success) { + *(WGPUDevice*)userdata1 = device; + } else { + fprintf(stderr, "Failed to get WebGPU device: %s\n", message.data); + return; + } + *(bool*)userdata2 = true; +} + +void device_lost_callback( + const WGPUDevice* device, WGPUDeviceLostReason reason, + WGPUStringView message, void* userdata1, void* userdata2 +) { + (void)userdata1; + (void)userdata2; + fprintf(stderr, "Device 0x%zx lost with reason 0x%zx: %s\n", (size_t)*device, (size_t)reason, message.data); +} + +void uncaptured_error_callback( + const WGPUDevice* device, WGPUErrorType error, + WGPUStringView message, void* userdata1, void* userdata2 +) { + (void)userdata1; + (void)userdata2; + fprintf(stderr, "Device 0x%zx has uncaptured error 0x%zx: %s\n", (size_t)*device, (size_t)error, message.data); +} diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 3ce1c0191..fe181bcde 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -115,6 +115,10 @@ extern "C" { * VK_USE_PLATFORM_WIN32_KHR) so we offer our replacement symbols after it. */ +#if defined(GLFW_INCLUDE_WEBGPU) + #include +#endif /* WebGPU header */ + /* It is customary to use APIENTRY for OpenGL function pointer declarations on * all platforms. Additionally, the Windows OpenGL header needs APIENTRY. */ @@ -6530,6 +6534,41 @@ GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance, GLFWwindow* window #endif /*VK_VERSION_1_0*/ +#if defined(WEBGPU_H_) + +/*! @brief Provide the address of the `wgpuInstanceCreateSurface` function to GLFW. + * + * This function passes the address provided for the `wgpuInstanceCreateSurface` function + * to GLFW. + * + * @param[in] addr The address of the `wgpuInstanceCreateSurface` function. + * + * @since Added in version 3.5 + * + * @ingroup webgpu + */ +GLFWAPI void glfwSetWGPUInstanceCreateSurfaceAddr(WGPUSurface (*addr)(WGPUInstance, const WGPUSurfaceDescriptor*)); + +/*! @brief Creates a WebGPU surface for the specified window. + * + * This function creates a WebGPU surface for the specified window. + * + * If the surface could not be created this function returns `NULL`. + * + * It is the callers responsibility to destroy the surface. The surface + * must be destroyed using `wgpuSurfaceRelease`. + * + * @param[in] instance The WebGPU instance to create the surface in. + * @param[in] window The window to create the surface for. + * @return The handle of the surface. This is `NULL` if an error occurred. + * + * @since Added in version 3.5 + * + * @ingroup webgpu + */ +GLFWAPI WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window); + +#endif /*WEBGPU_H_*/ /************************************************************************* * Global definition cleanup diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2cbe8a733..0b482ac33 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,9 +3,10 @@ add_library(glfw ${GLFW_LIBRARY_TYPE} "${GLFW_SOURCE_DIR}/include/GLFW/glfw3.h" "${GLFW_SOURCE_DIR}/include/GLFW/glfw3native.h" internal.h platform.h mappings.h - context.c init.c input.c monitor.c platform.c vulkan.c window.c - egl_context.c osmesa_context.c null_platform.h null_joystick.h - null_init.c null_monitor.c null_window.c null_joystick.c) + context.c init.c input.c monitor.c platform.c vulkan.c webgpu.c + window.c egl_context.c osmesa_context.c null_platform.h + null_joystick.h null_init.c null_monitor.c null_window.c + null_joystick.c) # The time, thread and module code is shared between all backends on a given OS, # including the null backend, which still needs those bits to be functional @@ -128,7 +129,8 @@ set_target_properties(glfw PROPERTIES C_STANDARD 99 C_EXTENSIONS OFF DEFINE_SYMBOL _GLFW_BUILD_DLL - FOLDER "GLFW3") + FOLDER "GLFW3" + EXPORT_COMPILE_COMMANDS ON) target_include_directories(glfw PUBLIC "$" diff --git a/src/cocoa_init.m b/src/cocoa_init.m index 15dc4ec4c..0df7a330a 100644 --- a/src/cocoa_init.m +++ b/src/cocoa_init.m @@ -564,7 +564,8 @@ GLFWbool _glfwConnectCocoa(int platformID, _GLFWplatform* platform) .getEGLNativeWindow = _glfwGetEGLNativeWindowCocoa, .getRequiredInstanceExtensions = _glfwGetRequiredInstanceExtensionsCocoa, .getPhysicalDevicePresentationSupport = _glfwGetPhysicalDevicePresentationSupportCocoa, - .createWindowSurface = _glfwCreateWindowSurfaceCocoa + .createWindowSurface = _glfwCreateWindowSurfaceCocoa, + .createWindowWGPUSurface = _glfwCreateWindowWGPUSurfaceCocoa }; *platform = cocoa; diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h index 4d1d66ae0..f7fa7b6f0 100644 --- a/src/cocoa_platform.h +++ b/src/cocoa_platform.h @@ -276,6 +276,8 @@ void _glfwGetRequiredInstanceExtensionsCocoa(char** extensions); GLFWbool _glfwGetPhysicalDevicePresentationSupportCocoa(VkInstance instance, VkPhysicalDevice device, uint32_t queuefamily); VkResult _glfwCreateWindowSurfaceCocoa(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface); +WGPUSurface _glfwCreateWindowWGPUSurfaceCocoa(WGPUInstance instance, _GLFWwindow* window); + void _glfwFreeMonitorCocoa(_GLFWmonitor* monitor); void _glfwGetMonitorPosCocoa(_GLFWmonitor* monitor, int* xpos, int* ypos); void _glfwGetMonitorContentScaleCocoa(_GLFWmonitor* monitor, float* xscale, float* yscale); diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 2bff22a6b..8bfe71675 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -2020,6 +2020,28 @@ VkResult _glfwCreateWindowSurfaceCocoa(VkInstance instance, } // autoreleasepool } +typedef struct WGPUSurfaceSourceMetalLayer +{ + WGPUChainedStruct chain; + void * layer; +} WGPUSurfaceSourceMetalLayer; + +WGPUSurface _glfwCreateWindowWGPUSurfaceCocoa(WGPUInstance instance, _GLFWwindow* window) +{ + [window->ns.view setLayer:window->ns.layer]; + [window->ns.view setWantsLayer:YES]; + + WGPUSurfaceSourceMetalLayer metalSurface; + metalSurface.chain.next = NULL; + metalSurface.chain.sType = WGPUSType_SurfaceSourceMetalLayer; + metalSurface.layer = window->ns.layer; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &metalSurface.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, SIZE_MAX }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); +} ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// diff --git a/src/internal.h b/src/internal.h index de703740f..fbd3acb0a 100644 --- a/src/internal.h +++ b/src/internal.h @@ -331,6 +331,45 @@ typedef PFN_vkVoidFunction (APIENTRY * PFN_vkGetInstanceProcAddr)(VkInstance,con typedef VkResult (APIENTRY * PFN_vkEnumerateInstanceExtensionProperties)(const char*,uint32_t*,VkExtensionProperties*); #define vkGetInstanceProcAddr _glfw.vk.GetInstanceProcAddr +// forward declare WebGPU types +typedef struct WGPUInstanceImpl* WGPUInstance; +typedef struct WGPUSurfaceImpl* WGPUSurface; + +typedef struct WGPUStringView +{ + char const * data; + size_t length; +} WGPUStringView; + +typedef enum WGPUSType +{ + WGPUSType_ShaderSourceSPIRV = 0x00000001, + WGPUSType_ShaderSourceWGSL = 0x00000002, + WGPUSType_RenderPassMaxDrawCount = 0x00000003, + WGPUSType_SurfaceSourceMetalLayer = 0x00000004, + WGPUSType_SurfaceSourceWindowsHWND = 0x00000005, + WGPUSType_SurfaceSourceXlibWindow = 0x00000006, + WGPUSType_SurfaceSourceWaylandSurface = 0x00000007, + WGPUSType_SurfaceSourceAndroidNativeWindow = 0x00000008, + WGPUSType_SurfaceSourceXCBWindow = 0x00000009, + WGPUSType_Force32 = 0x7FFFFFFF +} WGPUSType; + +typedef struct WGPUChainedStruct +{ + struct WGPUChainedStruct const * next; + WGPUSType sType; +} WGPUChainedStruct; + +typedef struct WGPUSurfaceDescriptor +{ + WGPUChainedStruct const * nextInChain; + WGPUStringView label; +} WGPUSurfaceDescriptor; + +typedef WGPUSurface (*PFN_wgpuInstanceCreateSurface)(WGPUInstance, const WGPUSurfaceDescriptor*); +#define wgpuInstanceCreateSurface _glfw.wgpu.instanceCreateSurface + #include "platform.h" #define GLFW_NATIVE_INCLUDE_NONE @@ -758,6 +797,8 @@ struct _GLFWplatform void (*getRequiredInstanceExtensions)(char**); GLFWbool (*getPhysicalDevicePresentationSupport)(VkInstance,VkPhysicalDevice,uint32_t); VkResult (*createWindowSurface)(VkInstance,_GLFWwindow*,const VkAllocationCallbacks*,VkSurfaceKHR*); + // webgpu + WGPUSurface (*createWindowWGPUSurface)(WGPUInstance, _GLFWwindow*); }; // Library global data @@ -874,6 +915,10 @@ struct _GLFWlibrary GLFWbool EXT_headless_surface; } vk; + struct { + PFN_wgpuInstanceCreateSurface instanceCreateSurface; + } wgpu; + struct { GLFWmonitorfun monitor; GLFWjoystickfun joystick; diff --git a/src/webgpu.c b/src/webgpu.c new file mode 100644 index 000000000..e330bd8f9 --- /dev/null +++ b/src/webgpu.c @@ -0,0 +1,56 @@ +//======================================================================== +// GLFW 3.5 - www.glfw.org +//------------------------------------------------------------------------ +// Copyright (c) Sebastian Dawid +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== + +#include "internal.h" + +#include +#include + +GLFWAPI void glfwSetWGPUInstanceCreateSurfaceAddr(WGPUSurface (*addr)(WGPUInstance, const WGPUSurfaceDescriptor*)) +{ + _GLFW_REQUIRE_INIT() + _glfw.wgpu.instanceCreateSurface = addr; +} + +GLFWAPI WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* handle) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL) + + _GLFWwindow* window = (_GLFWwindow*)handle; + + assert(window != NULL); + assert(instance != NULL); + assert(_glfw.wgpu.instanceCreateSurface != NULL); + + if (window->context.client != GLFW_NO_API) + { + _glfwInputError(GLFW_INVALID_VALUE, + "WebGPU: Window surface creation requires the window to have the client API set to GLFW_NO_API"); + return NULL; + }; + + return _glfw.platform.createWindowWGPUSurface(instance, window); +} diff --git a/src/win32_init.c b/src/win32_init.c index 6b6e9d08e..1a66b976f 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -669,7 +669,8 @@ GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform) .getEGLNativeWindow = _glfwGetEGLNativeWindowWin32, .getRequiredInstanceExtensions = _glfwGetRequiredInstanceExtensionsWin32, .getPhysicalDevicePresentationSupport = _glfwGetPhysicalDevicePresentationSupportWin32, - .createWindowSurface = _glfwCreateWindowSurfaceWin32 + .createWindowSurface = _glfwCreateWindowSurfaceWin32, + .createWindowWGPUSurface = _glfwCreateWindowWGPUSurfaceWin32 }; *platform = win32; diff --git a/src/win32_platform.h b/src/win32_platform.h index 49cceba6d..905c86bbc 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -544,6 +544,8 @@ void _glfwGetRequiredInstanceExtensionsWin32(char** extensions); GLFWbool _glfwGetPhysicalDevicePresentationSupportWin32(VkInstance instance, VkPhysicalDevice device, uint32_t queuefamily); VkResult _glfwCreateWindowSurfaceWin32(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface); +WGPUSurface _glfwCreateWindowWGPUSurfaceWin32(WGPUInstance instance, _GLFWwindow* window); + void _glfwFreeMonitorWin32(_GLFWmonitor* monitor); void _glfwGetMonitorPosWin32(_GLFWmonitor* monitor, int* xpos, int* ypos); void _glfwGetMonitorContentScaleWin32(_GLFWmonitor* monitor, float* xscale, float* yscale); diff --git a/src/win32_window.c b/src/win32_window.c index 6427a673e..ab31a90d8 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -2562,6 +2562,28 @@ VkResult _glfwCreateWindowSurfaceWin32(VkInstance instance, return err; } +typedef struct WGPUSurfaceSourceWindowsHWND +{ + WGPUChainedStruct chain; + void * hinstance; + void * hwnd; +} WGPUSurfaceSourceWindowsHWND; + +WGPUSurface _glfwCreateWindowWGPUSurfaceWin32(WGPUInstance instance, _GLFWwindow *window) +{ + WGPUSurfaceSourceWindowsHWND windowsSurface; + windowsSurface.chain.sType = WGPUSType_SurfaceSourceWindowsHWND; + windowsSurface.chain.next = NULL; + windowsSurface.hinstance = _glfw.win32.instance; + windowsSurface.hwnd = window->win32.handle; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &windowsSurface.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, SIZE_MAX }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); +} + GLFWAPI HWND glfwGetWin32Window(GLFWwindow* handle) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); diff --git a/src/wl_init.c b/src/wl_init.c index ef9e45036..f6490c913 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -515,7 +515,8 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform) .getEGLNativeWindow = _glfwGetEGLNativeWindowWayland, .getRequiredInstanceExtensions = _glfwGetRequiredInstanceExtensionsWayland, .getPhysicalDevicePresentationSupport = _glfwGetPhysicalDevicePresentationSupportWayland, - .createWindowSurface = _glfwCreateWindowSurfaceWayland + .createWindowSurface = _glfwCreateWindowSurfaceWayland, + .createWindowWGPUSurface = _glfwCreateWindowWGPUSurfaceWayland }; void* module = _glfwPlatformLoadModule("libwayland-client.so.0"); diff --git a/src/wl_platform.h b/src/wl_platform.h index c3e456931..1f910319a 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -678,6 +678,8 @@ void _glfwGetRequiredInstanceExtensionsWayland(char** extensions); GLFWbool _glfwGetPhysicalDevicePresentationSupportWayland(VkInstance instance, VkPhysicalDevice device, uint32_t queuefamily); VkResult _glfwCreateWindowSurfaceWayland(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface); +WGPUSurface _glfwCreateWindowWGPUSurfaceWayland(WGPUInstance instance, _GLFWwindow* window); + void _glfwFreeMonitorWayland(_GLFWmonitor* monitor); void _glfwGetMonitorPosWayland(_GLFWmonitor* monitor, int* xpos, int* ypos); void _glfwGetMonitorContentScaleWayland(_GLFWmonitor* monitor, float* xscale, float* yscale); diff --git a/src/wl_window.c b/src/wl_window.c index 4220d17e0..86e4e062c 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -3324,6 +3324,27 @@ VkResult _glfwCreateWindowSurfaceWayland(VkInstance instance, return err; } +typedef struct WGPUSurfaceSourceWaylandSurface +{ + WGPUChainedStruct chain; + void * display; + void * surface; +} WGPUSurfaceSourceWaylandSurface; + +WGPUSurface _glfwCreateWindowWGPUSurfaceWayland(WGPUInstance instance, _GLFWwindow* window) +{ + WGPUSurfaceSourceWaylandSurface waylandSurface; + waylandSurface.chain.sType = WGPUSType_SurfaceSourceWaylandSurface; + waylandSurface.chain.next = NULL; + waylandSurface.surface = window->wl.surface; + waylandSurface.display = _glfw.wl.display; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &waylandSurface.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, SIZE_MAX }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); +} ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// diff --git a/src/x11_init.c b/src/x11_init.c index 6b34c2639..c90babe19 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -1246,7 +1246,8 @@ GLFWbool _glfwConnectX11(int platformID, _GLFWplatform* platform) .getEGLNativeWindow = _glfwGetEGLNativeWindowX11, .getRequiredInstanceExtensions = _glfwGetRequiredInstanceExtensionsX11, .getPhysicalDevicePresentationSupport = _glfwGetPhysicalDevicePresentationSupportX11, - .createWindowSurface = _glfwCreateWindowSurfaceX11 + .createWindowSurface = _glfwCreateWindowSurfaceX11, + .createWindowWGPUSurface = _glfwCreateWindowWGPUSurfaceX11 }; // HACK: If the application has left the locale as "C" then both wide diff --git a/src/x11_platform.h b/src/x11_platform.h index 1bfeaab49..81c184c6f 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -964,6 +964,8 @@ void _glfwGetRequiredInstanceExtensionsX11(char** extensions); GLFWbool _glfwGetPhysicalDevicePresentationSupportX11(VkInstance instance, VkPhysicalDevice device, uint32_t queuefamily); VkResult _glfwCreateWindowSurfaceX11(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface); +WGPUSurface _glfwCreateWindowWGPUSurfaceX11(WGPUInstance instance, _GLFWwindow* window); + void _glfwFreeMonitorX11(_GLFWmonitor* monitor); void _glfwGetMonitorPosX11(_GLFWmonitor* monitor, int* xpos, int* ypos); void _glfwGetMonitorContentScaleX11(_GLFWmonitor* monitor, float* xscale, float* yscale); diff --git a/src/x11_window.c b/src/x11_window.c index 986bfb93b..77274ce36 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -3282,6 +3282,55 @@ VkResult _glfwCreateWindowSurfaceX11(VkInstance instance, } } +typedef struct WGPUSurfaceSourceXCBWindow +{ + WGPUChainedStruct chain; + void * connection; + uint32_t window; +} WGPUSurfaceSourceXCBWindow; + +typedef struct WGPUSurfaceSourceXlibWindow +{ + WGPUChainedStruct chain; + void * display; + uint64_t window; +} WGPUSurfaceSourceXlibWindow; + +WGPUSurface _glfwCreateWindowWGPUSurfaceX11(WGPUInstance instance, _GLFWwindow* window) +{ + WGPUSurfaceDescriptor surfaceDescriptor; + + if (_glfw.x11.x11xcb.handle) + { + WGPUSurfaceSourceXCBWindow xcbSurface; + xcbSurface.chain.sType = WGPUSType_SurfaceSourceXCBWindow; + xcbSurface.chain.next = NULL; + xcb_connection_t* connection = XGetXCBConnection(_glfw.x11.display); + if (!connection) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "X11: Failed to retrieve XCB connection"); + return NULL; + } + xcbSurface.connection = connection; + xcbSurface.window = window->x11.handle; + surfaceDescriptor.nextInChain = &xcbSurface.chain; + } + else + { + WGPUSurfaceSourceXlibWindow xlibSurface; + xlibSurface.chain.sType = WGPUSType_SurfaceSourceXlibWindow; + xlibSurface.chain.next = NULL; + xlibSurface.display = _glfw.x11.display; + xlibSurface.window = window->x11.handle; + surfaceDescriptor.nextInChain = &xlibSurface.chain; + } + + surfaceDescriptor.label = (WGPUStringView){ NULL, SIZE_MAX }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); +} + ////////////////////////////////////////////////////////////////////////// ////// GLFW native API //////