diff options
| -rw-r--r-- | .gitmodules | 3 | ||||
| -rw-r--r-- | CMakeLists.txt | 75 | ||||
| -rw-r--r-- | FETCH_DEPS.md | 29 | ||||
| -rw-r--r-- | HOWTO.md | 7 | ||||
| -rw-r--r-- | PROJECT_CONTEXT.md | 14 | ||||
| -rw-r--r-- | scripts/project_init.bat | 17 | ||||
| -rwxr-xr-x | scripts/project_init.sh | 15 | ||||
| -rw-r--r-- | src/audio/synth.cc | 10 | ||||
| -rw-r--r-- | src/audio/synth.h | 1 | ||||
| -rw-r--r-- | src/gpu/gpu.cc | 256 | ||||
| -rw-r--r-- | src/gpu/gpu.h | 2 | ||||
| -rw-r--r-- | src/main.cc | 27 | ||||
| -rw-r--r-- | src/platform.cc | 137 | ||||
| -rw-r--r-- | src/platform.h | 8 | ||||
| m--------- | third_party/wgpu-native | 0 |
15 files changed, 548 insertions, 53 deletions
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9b5813a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "third_party/wgpu-native"] + path = third_party/wgpu-native + url = https://github.com/gfx-rs/wgpu-native diff --git a/CMakeLists.txt b/CMakeLists.txt index 92d12f2..b06bd6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,43 @@ option(DEMO_SIZE_OPT "Enable size optimization flags" OFF) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# Search in both debug and release +set(WGPU_SEARCH_PATHS + ${CMAKE_CURRENT_SOURCE_DIR}/third_party/wgpu-native/target/debug + ${CMAKE_CURRENT_SOURCE_DIR}/third_party/wgpu-native/target/release +) + +find_library( + WGPU_LIBRARY NAMES libwgpu_native.a wgpu_native.lib wgpu_native + HINTS ${WGPU_SEARCH_PATHS} + REQUIRED +) + +include_directories( + src + third_party + third_party/wgpu-native/ffi + third_party/wgpu-native/ffi/webgpu-headers +) + +find_package(glfw3 REQUIRED) + +set(DEMO_LIBS glfw ${WGPU_LIBRARY}) + +# Platform-specific dependencies for wgpu-native +if (APPLE) + set_source_files_properties(src/platform.cc PROPERTIES COMPILE_FLAGS "-x objective-c++") + list(APPEND DEMO_LIBS + "-framework Metal" + "-framework Foundation" + "-framework Cocoa" + "-framework QuartzCore" + ) +else() + # Assume Linux/other POSIX-like systems might need these + list(APPEND DEMO_LIBS pthread m dl) +endif() + add_executable(demo64k src/main.cc src/platform.cc @@ -17,13 +54,7 @@ add_executable(demo64k src/audio/synth.cc ) -target_include_directories(demo64k PRIVATE - src - third_party -) - -find_package(glfw3 REQUIRED) -target_link_libraries(demo64k PRIVATE glfw) +target_link_libraries(demo64k PRIVATE ${DEMO_LIBS}) if (DEMO_SIZE_OPT) if (MSVC) @@ -43,9 +74,7 @@ if(DEMO_BUILD_TESTS) src/tests/test_window.cc src/audio/window.cc ) - target_include_directories(test_window PRIVATE - src - ) + target_include_directories(test_window PRIVATE src) add_test(NAME HammingWindowTest COMMAND test_window) add_executable(test_synth @@ -54,24 +83,19 @@ if(DEMO_BUILD_TESTS) src/audio/idct.cc src/audio/window.cc ) - target_include_directories(test_synth PRIVATE - src - ) + target_include_directories(test_synth PRIVATE src) add_test(NAME SynthEngineTest COMMAND test_synth) add_executable(test_spectool src/tests/test_spectool.cc - src/audio/audio.cc # For miniaudio implementation + src/audio/audio.cc src/audio/window.cc src/audio/fdct.cc src/audio/synth.cc src/audio/idct.cc ) - target_include_directories(test_spectool PRIVATE - src - third_party - ) - add_dependencies(test_spectool spectool) + target_include_directories(test_spectool PRIVATE src third_party) + target_link_libraries(test_spectool PRIVATE ${DEMO_LIBS}) add_test(NAME SpectoolEndToEndTest COMMAND test_spectool) endif() @@ -86,16 +110,11 @@ if(DEMO_BUILD_TOOLS) src/audio/window.cc src/audio/synth.cc ) - target_include_directories(spectool PRIVATE - src - third_party - ) - target_link_libraries(spectool PRIVATE glfw) + target_include_directories(spectool PRIVATE src third_party) + target_link_libraries(spectool PRIVATE ${DEMO_LIBS}) add_executable(specview tools/specview.cc ) - target_include_directories(specview PRIVATE - src - ) -endif() + target_include_directories(specview PRIVATE src) +endif()
\ No newline at end of file diff --git a/FETCH_DEPS.md b/FETCH_DEPS.md index d16fb45..3d5cc0e 100644 --- a/FETCH_DEPS.md +++ b/FETCH_DEPS.md @@ -30,3 +30,32 @@ https://raw.githubusercontent.com/mackron/miniaudio/master/miniaudio.h and place it into: third_party/miniaudio.h + +## wgpu-native + +WebGPU implementation via wgpu-native. + +Source: +https://github.com/gfx-rs/wgpu-native + +### Automatic fetch + +Use one of the provided scripts: +- scripts/project_init.sh +- scripts/project_init.bat + +These scripts will run `git submodule update --init --recursive` to fetch `wgpu-native` and then build its static library. + +### Manual fetch + +Run the following commands in the project root directory: +```bash +git submodule add https://github.com/gfx-rs/wgpu-native third_party/wgpu-native +git submodule update --init --recursive +cd third_party/wgpu-native +make lib-native # Requires Rust toolchain and LLVM/Clang to be installed. +cd ../.. +``` + +Expected static library location (for linking): +- `third_party/wgpu-native/target/release/libwgpu_native.a` (or platform equivalent like `.lib` or `.dylib`) @@ -11,11 +11,18 @@ This document describes the common commands for building and testing the project ### Debug Build +To run the demo in fullscreen mode, use the `--fullscreen` command-line option: + ```bash cmake -S . -B build cmake --build build +./build/demo64k --fullscreen ``` +Keyboard Controls: +* `Esc`: Exit the demo. +* `F`: Toggle fullscreen mode. + ### Size-Optimized Build ```bash diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md index e607a19..1ee929c 100644 --- a/PROJECT_CONTEXT.md +++ b/PROJECT_CONTEXT.md @@ -23,6 +23,20 @@ Style: - Demoscene - No engine abstractions +Incoming tasks in no particular order: +- 1. add a fullscreen mode (as command line option) +- 2. parse the keyboard key. Exit the demo when 'esc' is pressed. Toggle full-screen when 'f' is pressed. +- 3. add binary crunchers for all platforms +- 4. add cross-compilation for PC+linux (x86_64) and PC+Windows (.exe binary) +- 5. implement a spectrogram editor for representing .spec with elementary + shapes (bezier curves, lines, random noise, rectangles...) as a mean + of compression +- 6. add a scripting tool to edit the demo (compiled into the binary at the end) +- 7. compile wgpu-native in optimized mode (not unoptimized) +- 8. add a #define STRIP_ALL to remove all unnecessary code for the final build + (for instance, command-line args parsing, or unnecessary options, constant + parameters to function calls, etc.) + ## Session Decisions and Current State ### Audio Engine (Synth): diff --git a/scripts/project_init.bat b/scripts/project_init.bat index 01d9ba2..b2e8320 100644 --- a/scripts/project_init.bat +++ b/scripts/project_init.bat @@ -12,4 +12,21 @@ if not exist third_party\miniaudio.h ( echo miniaudio.h already present. ) +:: wgpu-native submodule +if not exist third_party\wgpu-native\.git ( + echo Fetching wgpu-native submodule... + git submodule update --init --recursive +) else ( + echo wgpu-native submodule already present. +) + +if not exist third_party\wgpu-native\target\release\wgpu_native.lib ( + echo Building wgpu-native static library... + pushd third_party\wgpu-native + cargo build --release :: Requires Rust toolchain and LLVM/Clang to be installed. + popd +) else ( + echo wgpu-native static library already built. +) + echo Done. diff --git a/scripts/project_init.sh b/scripts/project_init.sh index 40f9457..db24c8f 100755 --- a/scripts/project_init.sh +++ b/scripts/project_init.sh @@ -14,4 +14,19 @@ else echo "miniaudio.h already present." fi +# wgpu-native submodule +if [ ! -d third_party/wgpu-native ]; then + echo "Fetching wgpu-native submodule..." + git submodule update --init --recursive +else + echo "wgpu-native submodule already present." +fi + +if [ ! -f third_party/wgpu-native/target/release/libwgpu_native.a ]; then + echo "Building wgpu-native static library..." + (cd third_party/wgpu-native && make lib-native) +else + echo "wgpu-native static library already built." +fi + echo "Done." diff --git a/src/audio/synth.cc b/src/audio/synth.cc index 451fcdb..98c12d9 100644 --- a/src/audio/synth.cc +++ b/src/audio/synth.cc @@ -26,10 +26,12 @@ static struct { } g_synth_data; static Voice g_voices[MAX_VOICES]; +static float g_current_output_peak = 0.0f; // Global peak for visualization void synth_init() { memset(&g_synth_data, 0, sizeof(g_synth_data)); memset(g_voices, 0, sizeof(g_voices)); + g_current_output_peak = 0.0f; } void synth_shutdown() { @@ -123,6 +125,8 @@ void synth_render(float *output_buffer, int num_frames) { float window[WINDOW_SIZE]; hamming_window_512(window); + float current_peak_in_frame = 0.0f; + for (int i = 0; i < num_frames; ++i) { float left_sample = 0.0f; float right_sample = 0.0f; @@ -165,7 +169,11 @@ void synth_render(float *output_buffer, int num_frames) { output_buffer[i * 2] = left_sample; output_buffer[i * 2 + 1] = right_sample; + + current_peak_in_frame = + fmaxf(current_peak_in_frame, fmaxf(fabsf(left_sample), fabsf(right_sample))); } + g_current_output_peak = current_peak_in_frame; } int synth_get_active_voice_count() { @@ -177,3 +185,5 @@ int synth_get_active_voice_count() { } return count; } + +float synth_get_output_peak() { return g_current_output_peak; } diff --git a/src/audio/synth.h b/src/audio/synth.h index eadde10..fe28e8d 100644 --- a/src/audio/synth.h +++ b/src/audio/synth.h @@ -23,3 +23,4 @@ void synth_commit_update(int spectrogram_id); void synth_trigger_voice(int spectrogram_id, float volume, float pan); void synth_render(float *output_buffer, int num_frames); int synth_get_active_voice_count(); +float synth_get_output_peak(); diff --git a/src/gpu/gpu.cc b/src/gpu/gpu.cc index e538d5d..a1da2db 100644 --- a/src/gpu/gpu.cc +++ b/src/gpu/gpu.cc @@ -1,8 +1,258 @@ #include "gpu.h" +#include "platform.h" -void gpu_init(GLFWwindow *) { +#include <GLFW/glfw3.h> +#include <webgpu.h> +#include <wgpu.h> + +#include <algorithm> +#include <cassert> +#include <cstring> +#include <iostream> +#include <vector> + +static WGPUInstance g_instance = nullptr; +static WGPUAdapter g_adapter = nullptr; +static WGPUDevice g_device = nullptr; +static WGPUQueue g_queue = nullptr; +static WGPUSurface g_surface = nullptr; +static WGPUSurfaceConfiguration g_config = {}; + +static WGPURenderPipeline g_render_pipeline = nullptr; +static WGPUBuffer g_uniform_buffer = nullptr; +static WGPUBindGroup g_bind_group = nullptr; + +static WGPUStringView str_view(const char *str) { + if (!str) return {nullptr, 0}; + return {str, strlen(str)}; +} + +static void handle_request_adapter(WGPURequestAdapterStatus status, + WGPUAdapter adapter, WGPUStringView message, + void *userdata1, void *userdata2) { + if (status == WGPURequestAdapterStatus_Success) { + *((WGPUAdapter *)userdata1) = adapter; + } else { + printf("Request adapter failed: %.*s\n", (int)message.length, message.data); + } +} + +static void handle_request_device(WGPURequestDeviceStatus status, + WGPUDevice device, WGPUStringView message, + void *userdata1, void *userdata2) { + if (status == WGPURequestDeviceStatus_Success) { + *((WGPUDevice *)userdata1) = device; + } else { + printf("Request device failed: %.*s\n", (int)message.length, message.data); + } +} + +static void handle_device_error(WGPUDevice const *device, WGPUErrorType type, + WGPUStringView message, void *userdata1, + void *userdata2) { + printf("WebGPU Error: %.*s\n", (int)message.length, message.data); } -void gpu_draw() { + +const char *shader_wgsl_code = R"( +struct Uniforms { + audio_peak : f32, +}; + +@group(0) @binding(0) var<uniform> uniforms : Uniforms; + +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4<f32> { + let PI = 3.14159265; + let num_sides = 7.0; + + let base_scale = 0.5; + let pulse_scale = 0.2 * uniforms.audio_peak; + let scale = base_scale + pulse_scale; + + let tri_idx = f32(vertex_index / 3u); + let sub_idx = vertex_index % 3u; + + if (sub_idx == 0u) { + return vec4<f32>(0.0, 0.0, 0.0, 1.0); + } + + let i = tri_idx + f32(sub_idx - 1u); + let angle = i * 2.0 * PI / num_sides; + let x = scale * cos(angle); + let y = scale * sin(angle); + + return vec4<f32>(x, y, 0.0, 1.0); +} + +@fragment +fn fs_main() -> @location(0) vec4<f32> { + let hue = uniforms.audio_peak * 0.5; + let r = sin(hue + 0.0) * 0.5 + 0.5; + let g = sin(hue + 2.0) * 0.5 + 0.5; + let b = sin(hue + 4.0) * 0.5 + 0.5; + return vec4<f32>(r, g, b, 1.0); +} +)"; + +void gpu_init(GLFWwindow *window) { + g_instance = wgpuCreateInstance(nullptr); + assert(g_instance); + + g_surface = platform_create_wgpu_surface(g_instance); + assert(g_surface); + + WGPURequestAdapterOptions adapter_opts = {}; + adapter_opts.compatibleSurface = g_surface; + adapter_opts.powerPreference = WGPUPowerPreference_HighPerformance; + + wgpuInstanceRequestAdapter( + g_instance, &adapter_opts, + {nullptr, WGPUCallbackMode_WaitAnyOnly, handle_request_adapter, &g_adapter, + nullptr}); + + while (!g_adapter) { + wgpuInstanceWaitAny(g_instance, 0, nullptr, 0); + } + + WGPUDeviceDescriptor device_desc = {}; + device_desc.label = str_view("Demo Device"); + device_desc.uncapturedErrorCallbackInfo.callback = handle_device_error; + + wgpuAdapterRequestDevice( + g_adapter, &device_desc, + {nullptr, WGPUCallbackMode_WaitAnyOnly, handle_request_device, &g_device, + nullptr}); + + while (!g_device) { + wgpuInstanceWaitAny(g_instance, 0, nullptr, 0); + } + + g_queue = wgpuDeviceGetQueue(g_device); + + WGPUSurfaceCapabilities caps = {}; + wgpuSurfaceGetCapabilities(g_surface, g_adapter, &caps); + WGPUTextureFormat swap_chain_format = caps.formats[0]; + + WGPUShaderSourceWGSL wgsl_src = {}; + wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; + wgsl_src.code = str_view(shader_wgsl_code); + + WGPUShaderModuleDescriptor shader_module_desc = {}; + shader_module_desc.nextInChain = &wgsl_src.chain; + shader_module_desc.label = str_view("Demo Shader"); + + WGPUShaderModule shader_module = + wgpuDeviceCreateShaderModule(g_device, &shader_module_desc); + + WGPUBufferDescriptor uniform_buffer_desc = {}; + uniform_buffer_desc.label = str_view("Uniform Buffer"); + uniform_buffer_desc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst; + uniform_buffer_desc.size = sizeof(float); + g_uniform_buffer = wgpuDeviceCreateBuffer(g_device, &uniform_buffer_desc); + + WGPUBindGroupLayoutEntry bgl_entry = {}; + bgl_entry.binding = 0; + bgl_entry.visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment; + bgl_entry.buffer.type = WGPUBufferBindingType_Uniform; + bgl_entry.buffer.minBindingSize = sizeof(float); + + WGPUBindGroupLayoutDescriptor bgl_desc = {}; + bgl_desc.entryCount = 1; + bgl_desc.entries = &bgl_entry; + WGPUBindGroupLayout bind_group_layout = + wgpuDeviceCreateBindGroupLayout(g_device, &bgl_desc); + + WGPUBindGroupEntry bg_entry = {}; + bg_entry.binding = 0; + bg_entry.buffer = g_uniform_buffer; + bg_entry.size = sizeof(float); + + WGPUBindGroupDescriptor bg_desc = {}; + bg_desc.layout = bind_group_layout; + bg_desc.entryCount = 1; + bg_desc.entries = &bg_entry; + g_bind_group = wgpuDeviceCreateBindGroup(g_device, &bg_desc); + + WGPUPipelineLayoutDescriptor pl_desc = {}; + pl_desc.bindGroupLayoutCount = 1; + pl_desc.bindGroupLayouts = &bind_group_layout; + WGPUPipelineLayout pipeline_layout = + wgpuDeviceCreatePipelineLayout(g_device, &pl_desc); + + WGPUColorTargetState color_target = {}; + color_target.format = swap_chain_format; + color_target.writeMask = WGPUColorWriteMask_All; + + WGPUFragmentState fragment_state = {}; + fragment_state.module = shader_module; + fragment_state.entryPoint = str_view("fs_main"); + fragment_state.targetCount = 1; + fragment_state.targets = &color_target; + + WGPURenderPipelineDescriptor pipeline_desc = {}; + pipeline_desc.layout = pipeline_layout; + pipeline_desc.vertex.module = shader_module; + pipeline_desc.vertex.entryPoint = str_view("vs_main"); + pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; + pipeline_desc.multisample.count = 1; + pipeline_desc.multisample.mask = 0xFFFFFFFF; + pipeline_desc.fragment = &fragment_state; + + g_render_pipeline = wgpuDeviceCreateRenderPipeline(g_device, &pipeline_desc); + + int width, height; + glfwGetFramebufferSize(window, &width, &height); + g_config.device = g_device; + g_config.format = swap_chain_format; + g_config.width = width; + g_config.height = height; + g_config.presentMode = WGPUPresentMode_Fifo; + g_config.alphaMode = WGPUCompositeAlphaMode_Opaque; + + wgpuSurfaceConfigure(g_surface, &g_config); } -void gpu_shutdown() { + +void gpu_draw(float audio_peak) { + WGPUSurfaceTexture surface_texture; + wgpuSurfaceGetCurrentTexture(g_surface, &surface_texture); + if (surface_texture.status != WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal && + surface_texture.status != WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal) + return; + + WGPUTextureView view = wgpuTextureCreateView(surface_texture.texture, nullptr); + + wgpuQueueWriteBuffer(g_queue, g_uniform_buffer, 0, &audio_peak, sizeof(float)); + + WGPUCommandEncoderDescriptor encoder_desc = {}; + WGPUCommandEncoder encoder = + wgpuDeviceCreateCommandEncoder(g_device, &encoder_desc); + + WGPURenderPassColorAttachment color_attachment = {}; + color_attachment.view = view; + color_attachment.loadOp = WGPULoadOp_Clear; + color_attachment.storeOp = WGPUStoreOp_Store; + color_attachment.clearValue = {0.1, 0.2, 0.3, 1.0}; + + WGPURenderPassDescriptor render_pass_desc = {}; + render_pass_desc.colorAttachmentCount = 1; + render_pass_desc.colorAttachments = &color_attachment; + + WGPURenderPassEncoder pass = + wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc); + + wgpuRenderPassEncoderSetPipeline(pass, g_render_pipeline); + wgpuRenderPassEncoderSetBindGroup(pass, 0, g_bind_group, 0, nullptr); + // Drawing a heptagon with TriangleList: 7 triangles * 3 vertices = 21 + wgpuRenderPassEncoderDraw(pass, 21, 1, 0, 0); + wgpuRenderPassEncoderEnd(pass); + + WGPUCommandBufferDescriptor cmd_desc = {}; + WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, &cmd_desc); + wgpuQueueSubmit(g_queue, 1, &commands); + wgpuSurfacePresent(g_surface); + + wgpuTextureViewRelease(view); + wgpuTextureRelease(surface_texture.texture); } + +void gpu_shutdown() {}
\ No newline at end of file diff --git a/src/gpu/gpu.h b/src/gpu/gpu.h index d36f618..127ca42 100644 --- a/src/gpu/gpu.h +++ b/src/gpu/gpu.h @@ -2,5 +2,5 @@ struct GLFWwindow; void gpu_init(GLFWwindow *window); -void gpu_draw(); +void gpu_draw(float audio_peak); void gpu_shutdown(); diff --git a/src/main.cc b/src/main.cc index 56d755c..315fa10 100644 --- a/src/main.cc +++ b/src/main.cc @@ -26,15 +26,23 @@ void generate_tone(float *buffer, float freq) { } } -int main() { - platform_init(); +int main(int argc, char **argv) { + bool fullscreen_enabled = false; + for (int i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--fullscreen") == 0) { + fullscreen_enabled = true; + break; + } + } + + platform_init_window(fullscreen_enabled); gpu_init(platform_get_window()); audio_init(); generate_tone(g_spec_buffer_a, 440.0f); // A4 - generate_tone(g_spec_buffer_b, 880.0f); // A5 + generate_tone(g_spec_buffer_b, 0.0f); // A5 - Spectrogram spec = {g_spec_buffer_a, g_spec_buffer_b, SPEC_FRAMES}; + const Spectrogram spec = {g_spec_buffer_a, g_spec_buffer_b, SPEC_FRAMES}; int tone_id = synth_register_spectrogram(&spec); double last_beat_time = 0.0; @@ -45,21 +53,22 @@ int main() { double current_time = platform_get_time(); if (current_time - last_beat_time > SECONDS_PER_BEAT) { - synth_trigger_voice(tone_id, 0.5f, 0.0f); + const float pan = (beat_count & 1) ? -.8 : .8; + synth_trigger_voice(tone_id, 0.8f, pan); last_beat_time = current_time; beat_count++; - if (beat_count == 8) { + if (beat_count % 4 == 0) { // Time to update the sound! float *back_buffer = synth_begin_update(tone_id); if (back_buffer) { - generate_tone(back_buffer, 220.0f); // A3 + generate_tone(back_buffer, (beat_count % 8) == 4 ? 220.0f : 480.f); // A3 synth_commit_update(tone_id); } - } + } } - gpu_draw(); + gpu_draw(synth_get_output_peak()); audio_update(); } diff --git a/src/platform.cc b/src/platform.cc index bfb566c..09cc7ac 100644 --- a/src/platform.cc +++ b/src/platform.cc @@ -1,11 +1,55 @@ #include "platform.h" + +#ifdef _WIN32 +#define GLFW_EXPOSE_NATIVE_WIN32 +#elif defined(__APPLE__) +#define GLFW_EXPOSE_NATIVE_COCOA +#else +#define GLFW_EXPOSE_NATIVE_X11 +#define GLFW_EXPOSE_NATIVE_WAYLAND +#endif + #include <GLFW/glfw3.h> +#include <GLFW/glfw3native.h> + +#ifdef __APPLE__ +#import <QuartzCore/CAMetalLayer.h> +#import <AppKit/NSWindow.h> +#import <AppKit/NSView.h> +#endif static GLFWwindow *window = nullptr; +static int windowed_x, windowed_y, windowed_w, windowed_h; +static bool g_is_fullscreen = false; -void platform_init() { +static void glfw_key_callback(GLFWwindow *cb_window, int key, int scancode, + int action, int mods) { + if (action == GLFW_PRESS) { + if (key == GLFW_KEY_ESCAPE) { + glfwSetWindowShouldClose(cb_window, GLFW_TRUE); + } else if (key == GLFW_KEY_F) { + platform_toggle_fullscreen(); + } + } +} + +void platform_init_window(bool fullscreen) { glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(1280, 720, "demo64k", nullptr, nullptr); + glfwSetKeyCallback(window, glfw_key_callback); + + g_is_fullscreen = fullscreen; + if (fullscreen) { + // Save current windowed mode dimensions before going fullscreen + glfwGetWindowPos(window, &windowed_x, &windowed_y); + glfwGetWindowSize(window, &windowed_w, &windowed_h); + + GLFWmonitor *monitor = glfwGetPrimaryMonitor(); + const GLFWvidmode *mode = glfwGetVideoMode(monitor); + glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, + mode->refreshRate); + } } void platform_shutdown() { @@ -13,18 +57,89 @@ void platform_shutdown() { glfwTerminate(); } -void platform_poll() { - glfwPollEvents(); -} +void platform_poll() { glfwPollEvents(); } -bool platform_should_close() { - return glfwWindowShouldClose(window); -} +bool platform_should_close() { return glfwWindowShouldClose(window); } + +void platform_toggle_fullscreen() { + g_is_fullscreen = !g_is_fullscreen; + if (g_is_fullscreen) { + glfwGetWindowPos(window, &windowed_x, &windowed_y); + glfwGetWindowSize(window, &windowed_w, &windowed_h); -GLFWwindow *platform_get_window() { - return window; + GLFWmonitor *monitor = glfwGetPrimaryMonitor(); + const GLFWvidmode *mode = glfwGetVideoMode(monitor); + glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, + mode->refreshRate); + } else { + glfwSetWindowMonitor(window, nullptr, windowed_x, windowed_y, windowed_w, + windowed_h, 0); + } } -double platform_get_time() { - return glfwGetTime(); +GLFWwindow *platform_get_window() { return window; } + +double platform_get_time() { return glfwGetTime(); } + +WGPUSurface platform_create_wgpu_surface(WGPUInstance instance) { +#if defined(GLFW_EXPOSE_NATIVE_COCOA) + id metal_layer = NULL; + NSWindow *ns_window = glfwGetCocoaWindow(window); + [ns_window.contentView setWantsLayer:YES]; + metal_layer = [CAMetalLayer layer]; + [ns_window.contentView setLayer:metal_layer]; + + WGPUSurfaceSourceMetalLayer metal_src = {}; + metal_src.chain.sType = WGPUSType_SurfaceSourceMetalLayer; + metal_src.layer = metal_layer; + + WGPUSurfaceDescriptor surface_desc = {}; + surface_desc.nextInChain = (const WGPUChainedStruct *)&metal_src; + + return wgpuInstanceCreateSurface(instance, &surface_desc); + +#elif defined(GLFW_EXPOSE_NATIVE_WIN32) + HWND hwnd = glfwGetWin32Window(window); + HINSTANCE hinstance = GetModuleHandle(NULL); + + WGPUSurfaceSourceWindowsHWND win_src = {}; + win_src.chain.sType = WGPUSType_SurfaceSourceWindowsHWND; + win_src.hinstance = hinstance; + win_src.hwnd = hwnd; + + WGPUSurfaceDescriptor surface_desc = {}; + surface_desc.nextInChain = (const WGPUChainedStruct *)&win_src; + + return wgpuInstanceCreateSurface(instance, &surface_desc); + +#elif defined(GLFW_EXPOSE_NATIVE_X11) || defined(GLFW_EXPOSE_NATIVE_WAYLAND) + if (glfwGetPlatform() == GLFW_PLATFORM_X11) { + Display *x11_display = glfwGetX11Display(); + Window x11_window = glfwGetX11Window(window); + + WGPUSurfaceSourceXlibWindow x11_src = {}; + x11_src.chain.sType = WGPUSType_SurfaceSourceXlibWindow; + x11_src.display = x11_display; + x11_src.window = x11_window; + + WGPUSurfaceDescriptor surface_desc = {}; + surface_desc.nextInChain = (const WGPUChainedStruct *)&x11_src; + + return wgpuInstanceCreateSurface(instance, &surface_desc); + } else if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) { + struct wl_display *wayland_display = glfwGetWaylandDisplay(); + struct wl_surface *wayland_surface = glfwGetWaylandWindow(window); + + WGPUSurfaceSourceWaylandSurface wl_src = {}; + wl_src.chain.sType = WGPUSType_SurfaceSourceWaylandSurface; + wl_src.display = wayland_display; + wl_src.surface = wayland_surface; + + WGPUSurfaceDescriptor surface_desc = {}; + surface_desc.nextInChain = (const WGPUChainedStruct *)&wl_src; + + return wgpuInstanceCreateSurface(instance, &surface_desc); + } +#endif + return nullptr; } diff --git a/src/platform.h b/src/platform.h index 4d9a1f9..2146e10 100644 --- a/src/platform.h +++ b/src/platform.h @@ -1,9 +1,15 @@ #pragma once +#include <webgpu.h> + struct GLFWwindow; -void platform_init(); +void platform_init_window(bool fullscreen); void platform_shutdown(); void platform_poll(); bool platform_should_close(); +void platform_toggle_fullscreen(); GLFWwindow *platform_get_window(); double platform_get_time(); + +// Creates a WebGPU surface for the current platform window. +WGPUSurface platform_create_wgpu_surface(WGPUInstance instance); diff --git a/third_party/wgpu-native b/third_party/wgpu-native new file mode 160000 +Subproject ba4deb5d935652f40c7e051b15cbb5d09721994 |
