HelloWorld!

This commit is contained in:
abux 2026-07-09 23:16:38 +01:00
commit f2f4830d57
26 changed files with 9404 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.zig-cache/
zig-pkg/

69
build.zig Normal file
View file

@ -0,0 +1,69 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const mod = b.addModule("platform", .{
.root_source_file = b.path("src/renderer.zig"),
.link_libc = true,
.link_libcpp = true,
.target = target,
.optimize = optimize,
});
const mod_tests = b.addTest(.{
.root_module = mod,
});
const run_mod_tests = b.addRunArtifact(mod_tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);
//
// DEPENDENCIES
//
const app = b.dependency("app", .{
.target = target,
.optimize = optimize,
});
mod.addImport("app", app.module("app"));
const platform = b.dependency("platform", .{
.target = target,
.optimize = optimize,
});
mod.addImport("platform_plugin", platform.module("platform"));
//
// SYSTEM DEPENDENCIES
//
const sdl = b.addTranslateC(.{
.target = target,
.optimize = optimize,
.root_source_file = b.path("./src/vendors/sdl3/sdl.h"),
});
mod.addImport("sdl", sdl.createModule());
mod.linkSystemLibrary("SDL3", .{});
const sdl3_glue = b.addTranslateC(.{
.target = target,
.optimize = optimize,
.root_source_file = b.path("./src/vendors/sdl3_glue/sdl3webgpu.h"),
});
mod.addImport("sdl_glue", sdl3_glue.createModule());
mod.addCSourceFile(.{ .file = b.path("./src/vendors/sdl3_glue/sdl3webgpu.c") });
const wgpu = b.addTranslateC(.{
.target = target,
.optimize = optimize,
.root_source_file = b.path("./src/vendors/wgpu/wgpu.h"),
});
mod.addImport("wgpu", wgpu.createModule());
mod.linkSystemLibrary("wgpu_native", .{});
}

15
build.zig.zon Normal file
View file

@ -0,0 +1,15 @@
.{
.name = .renderer,
.version = "0.0.1",
.dependencies = .{
.app = .{
.path = "../../app",
},
.platform = .{
.path = "../platform/",
},
},
.minimum_zig_version = "0.16.0",
.paths = .{""},
.fingerprint = 0xf199a19cb1dc8f03,
}

0
src/component/camera.zig Normal file
View file

1
src/component/root.zig Normal file
View file

@ -0,0 +1 @@
pub const Camera = @import("camera.zig");

40
src/plugin.zig Normal file
View file

@ -0,0 +1,40 @@
const App = @import("app");
const system = @import("system/root.zig");
const resource = @import("resource/root.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const plugin: App.plugin.Plugin = .{
.name = "Renderer",
.description = "WebGPU Renderer",
.build = build,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
fn build(
_: *App.plugin.Plugin,
ctx: App.plugin.Context,
) !void {
// --- RESOURCES ---
try ctx.app.resources.set(resource.Instance{});
try ctx.app.resources.set(resource.Device{});
try ctx.app.resources.set(resource.window{});
// --- INIT ---
try ctx.app.schedules.addMany(App.stage.Init, &.{
system.instance.init,
system.window.init,
system.device.init,
});
// --- DEINIT ---
try ctx.app.schedules.addMany(App.stage.Deinit, &.{
system.device.deinit,
system.window.deinit,
system.instance.deinit,
});
// --- INPUT ---
try ctx.app.schedules.addMany(App.stage.Inputs, &.{});
}

45
src/renderer.zig Normal file
View file

@ -0,0 +1,45 @@
const std = @import("std");
const App = @import("app");
const Platform = @import("platform_plugin");
pub const plugin = @import("plugin.zig").plugin;
pub const system = @import("system/root.zig");
pub const resource = @import("resource/root.zig");
test "Main" {
// --- APP ---
var app: App = .init(std.testing.allocator);
defer app.deinit();
// --- ON ERROR ---
errdefer {
app.schedules.run(App.stage.Deinit, .{ .app = &app }) catch unreachable;
app.deinit();
}
// --- ADD PLUGINS ---
try app.plugins.addMany(&.{
plugin,
Platform.plugin,
});
// --- BUILD ---
try app.plugins.build(.{
.app = &app,
});
// --- LIFETIME ---
try app.schedules.run(App.stage.Init, .{ .app = &app });
defer app.schedules.run(App.stage.Deinit, .{ .app = &app }) catch unreachable;
// --- MAIN LOOP ---
// while (!app.events.has(Platform.event.AppExit)) {
// try app.schedules.runMany(&.{
// App.stage.Inputs,
// App.stage.Update,
// App.stage.Draw,
// }, .{
// .app = &app,
// });
// }
}

4
src/resource/device.zig Normal file
View file

@ -0,0 +1,4 @@
const wgpu = @import("wgpu");
raw: ?*wgpu.WGPUDeviceImpl = null,
queue: ?*wgpu.WGPUQueueImpl = null,

View file

@ -0,0 +1,7 @@
const wgpu = @import("wgpu");
//
// FIELDS
//
raw: ?*wgpu.WGPUInstanceImpl = null,

4
src/resource/root.zig Normal file
View file

@ -0,0 +1,4 @@
// --- CORE ---
pub const Instance = @import("instance.zig");
pub const Device = @import("device.zig");
pub const window = @import("window.zig");

13
src/resource/window.zig Normal file
View file

@ -0,0 +1,13 @@
const sdl = @import("sdl");
const sdl_glue = @import("sdl_glue");
//
// FIELDS
//
title: []const u8 = "App",
width: u32 = 1280,
height: u32 = 720,
raw: ?*sdl.SDL_Window = null,
surface: ?*sdl_glue.WGPUSurfaceImpl = null,

233
src/system/device.zig Normal file
View file

@ -0,0 +1,233 @@
const std = @import("std");
const App = @import("app");
const wgpu = @import("wgpu");
const sdl_glue = @import("sdl_glue");
const Instance = @import("../resource/instance.zig");
const Device = @import("../resource/device.zig");
const Window = @import("../resource/window.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
const AdapterUserdata = struct {
adapter: ?*wgpu.WGPUAdapterImpl = null,
ended: bool = false,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
const DeviceUserdata = struct {
device: ?*wgpu.WGPUDeviceImpl = null,
ended: bool = false,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(ctx: App.scheduler.Context) !void {
// --- RESOURCES ---
const res = try ctx.app.resources.getMany(struct {
instance: *Instance,
window: *Window,
device: *Device,
});
//
// ADAPTER
//
// TODO: Mabe Mabe add other backends n stuff
var adapter_userdata: AdapterUserdata = .{};
_ = wgpu.wgpuInstanceRequestAdapter(
res.instance.raw,
&wgpu.WGPURequestAdapterOptions{
.backendType = wgpu.WGPUBackendType_Vulkan,
},
wgpu.WGPURequestAdapterCallbackInfo{
.callback = adapterCallback,
.userdata1 = &adapter_userdata,
},
);
// --- ADAPTER ---
const adapter = if (adapter_userdata.adapter) |v| v else return error.NoAdapter;
defer wgpu.wgpuAdapterRelease(adapter);
// --- ON ERROR ---
errdefer {
wgpu.wgpuAdapterRelease(adapter);
}
// --- GET LIMITS ---
var limits: wgpu.WGPULimits = .{};
switch (wgpu.wgpuAdapterGetLimits(adapter, &limits)) {
wgpu.WGPUStatus_Error => return error.FailedToGetAdapterLimits,
else => {},
}
// --- GET INFO ---
var info: wgpu.WGPUAdapterInfo = .{};
switch (wgpu.wgpuAdapterGetInfo(adapter, &info)) {
wgpu.WGPUStatus_Error => return error.FailedToGetAdapterInfo,
else => {},
}
// --- DEBUG LIMITS ---
std.debug.print("Adapter Limits:\n", .{});
std.debug.print("| Texture Dimentions: ({})1D, ({})2D, ({})3D, ({})Array Layers\n", .{
limits.maxTextureDimension1D,
limits.maxTextureDimension2D,
limits.maxTextureDimension3D,
limits.maxTextureArrayLayers,
});
// --- DEBUG INFO ---
std.debug.print("Adapter Info:\n", .{});
std.debug.print(
\\| Device: {s}
\\| Backend: {s}
\\
, .{
info.device.data,
switch (info.backendType) {
wgpu.WGPUBackendType_Vulkan => "Vulkan",
wgpu.WGPUBackendType_OpenGL => "OpenGL",
wgpu.WGPUBackendType_D3D11 => "D3D11",
wgpu.WGPUBackendType_D3D12 => "D3D12",
else => "idk", // TODO: idk 🥀
},
});
//
// DEVICE
//
// --- REQUEST DEVICE ---
var device_userdata: DeviceUserdata = .{};
_ = wgpu.wgpuAdapterRequestDevice(
adapter,
&wgpu.WGPUDeviceDescriptor{
.label = .{ .data = "MainDevice", .length = wgpu.WGPU_STRLEN },
.uncapturedErrorCallbackInfo = .{
.callback = uncapturedErrorCallback,
},
},
.{
.callback = deviceCallback,
.userdata1 = &device_userdata,
},
);
res.device.raw = device_userdata.device;
// --- ON ERROR ---
errdefer {
wgpu.wgpuDeviceRelease(res.device.raw);
res.device.raw = null;
}
// --- GET QUEUE ---
res.device.queue = wgpu.wgpuDeviceGetQueue(
res.device.raw,
) orelse return error.FailedToGetDeviceQueue;
var caps: sdl_glue.WGPUSurfaceCapabilities = .{};
_ = sdl_glue.wgpuSurfaceGetCapabilities(res.window.surface, @ptrCast(adapter), &caps); // TODO: Handle possible error
defer sdl_glue.wgpuSurfaceCapabilitiesFreeMembers(caps);
// --- CONFIGURE SURFACE ---
sdl_glue.wgpuSurfaceConfigure(
res.window.surface,
&sdl_glue.WGPUSurfaceConfiguration{
.device = @ptrCast(res.device.raw),
.format = caps.formats[0],
.width = res.window.width,
.height = res.window.height,
},
);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(ctx: App.scheduler.Context) !void {
// --- RESOURCES ---
const res = try ctx.app.resources.getMany(struct {
instance: *Instance,
device: *Device,
});
if (res.device.queue) |queue| {
wgpu.wgpuQueueRelease(queue);
res.device.queue = null;
}
if (res.device.raw) |raw| {
wgpu.wgpuDeviceRelease(raw);
res.device.raw = null;
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn uncapturedErrorCallback(
_: [*c]const wgpu.WGPUDevice,
err_type: wgpu.WGPUErrorType,
message: wgpu.WGPUStringView,
_: ?*anyopaque,
_: ?*anyopaque,
) callconv(.c) void {
std.debug.print("[{s}] {s}\n", .{
switch (err_type) {
wgpu.WGPUErrorType_Validation => "\x1b[33VALIDATION\x1b[0m",
wgpu.WGPUErrorType_OutOfMemory => "\x1b[31OUT_OF_MEMORY\x1b[0m",
else => "IDK",
},
message.data,
});
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn adapterCallback(
status: wgpu.WGPURequestAdapterStatus,
adapter: wgpu.WGPUAdapter,
message: wgpu.WGPUStringView,
raw_userdata: ?*anyopaque,
_: ?*anyopaque,
) callconv(.c) void {
// --- USERDATA ---
const userdata: *AdapterUserdata = @ptrCast(@alignCast(raw_userdata));
defer userdata.ended = true;
// --- STATUS ---
switch (status) {
wgpu.WGPURequestAdapterStatus_Success => userdata.adapter = adapter,
else => {
std.debug.print("Failed to get WebGPU adapter: {s}\n", .{
message.data,
});
},
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn deviceCallback(
status: wgpu.WGPURequestDeviceStatus,
device: wgpu.WGPUDevice,
message: wgpu.WGPUStringView,
raw_userdata: ?*anyopaque,
_: ?*anyopaque,
) callconv(.c) void {
// --- USERDATA ---
const userdata: *DeviceUserdata = @ptrCast(@alignCast(raw_userdata));
defer userdata.ended = true;
// --- STATUS ---
switch (status) {
wgpu.WGPURequestDeviceStatus_Success => userdata.device = device,
else => {
std.debug.print("Failed to get WebGPU adapter: {s}\n", .{
message.data,
});
},
}
}

29
src/system/instance.zig Normal file
View file

@ -0,0 +1,29 @@
const std = @import("std");
const App = @import("app");
const wgpu = @import("wgpu");
const Instance = @import("../resource/instance.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(ctx: App.scheduler.Context) !void {
const instance = ctx.app.resources.getPtr(
Instance,
) orelse return;
instance.raw = wgpu.wgpuCreateInstance(
&wgpu.WGPUInstanceDescriptor{},
) orelse return error.WGPUInstance;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(ctx: App.scheduler.Context) !void {
const instance = ctx.app.resources.getPtr(
Instance,
) orelse return;
if (instance.raw) |raw| {
wgpu.wgpuInstanceRelease(raw);
instance.raw = null;
}
}

4
src/system/root.zig Normal file
View file

@ -0,0 +1,4 @@
// --- CORE ---
pub const instance = @import("instance.zig");
pub const device = @import("device.zig");
pub const window = @import("window.zig");

50
src/system/window.zig Normal file
View file

@ -0,0 +1,50 @@
const App = @import("app");
const sdl = @import("sdl");
const sdl_glue = @import("sdl_glue");
const Instance = @import("../resource/instance.zig");
const Device = @import("../resource/device.zig");
const Window = @import("../resource/window.zig");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(ctx: App.scheduler.Context) !void {
// --- WINDOW ---
const res = try ctx.app.resources.getMany(struct {
instance: *Instance,
window: *Window,
});
// --- HANDLE ---
res.window.raw = sdl.SDL_CreateWindow(
res.window.title.ptr,
@intCast(res.window.width),
@intCast(res.window.height),
sdl.SDL_WINDOW_RESIZABLE,
) orelse return error.CreateWindow;
// --- SURFACE ---
res.window.surface = sdl_glue.SDL_GetWGPUSurface(
@ptrCast(res.instance.raw),
@ptrCast(res.window.raw),
) orelse return error.GetSurface;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(ctx: App.scheduler.Context) !void {
const win = ctx.app.resources.getPtr(
Window,
) orelse return;
if (win.surface) |surface| {
sdl_glue.wgpuSurfaceUnconfigure(surface);
sdl_glue.wgpuSurfaceRelease(surface);
win.surface = null;
}
if (win.raw) |raw| {
sdl.SDL_DestroyWindow(raw);
win.raw = null;
}
}

0
src/template/asset.zig Normal file
View file

View file

View file

286
src/template/registry.zig Normal file
View file

@ -0,0 +1,286 @@
const std = @import("std");
const App = @import("app");
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn Registry(
comptime K: type,
comptime V: type,
) type {
return struct {
const Self = @This();
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Metadata = struct {
source: ?[]const u8 = null,
reload: ?*const fn (*V, App.scheduler.Context) anyerror!void = null,
};
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Entry = struct {
key: K,
value: V,
};
//
// FIELDS
//
meta: std.ArrayList(Metadata),
lookup: std.AutoHashMap(K, usize),
entries: std.ArrayList(Entry),
alloc: std.mem.Allocator,
//
// LIFECYCLE
//
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn init(alloc: std.mem.Allocator) Self {
return .{
.meta = .empty,
.lookup = .init(alloc),
.entries = .empty,
.alloc = alloc,
};
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinit(self: *Self) void {
self.meta.deinit(self.alloc);
self.lookup.deinit();
self.entries.deinit(self.alloc);
}
//
// INSERTION
//
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn put(
self: *Self,
key: K,
value: V,
) !void {
if (self.lookup.contains(key)) return error.Duplicate;
try self.putNoDup(key, value, .{});
}
/// ----------------------------------------------------
/// Insert or replace
/// ----------------------------------------------------
pub fn upsert(
self: *Self,
key: K,
value: V,
) !?V {
if (self.lookup.contains(key)) {
const idx = self.lookup.get(key).?;
const old = self.entries.items[idx].value;
self.entries.items[idx].value = value;
return old;
}
try self.putNoDup(key, value, .{});
return null;
}
/// ----------------------------------------------------
/// Insert with metadata
/// ----------------------------------------------------
pub fn putWith(
self: *Self,
key: K,
value: V,
metadata: Metadata,
) !void {
if (self.lookup.contains(key)) return error.Duplicate;
try self.putNoDup(key, value, metadata);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn putNoDup(
self: *Self,
key: K,
value: V,
metadata: Metadata,
) !void {
try self.entries.append(self.alloc, .{ .key = key, .value = value });
errdefer _ = self.entries.swapRemove(self.entries.items.len - 1);
try self.meta.append(self.alloc, metadata);
errdefer _ = self.meta.swapRemove(self.meta.items.len - 1);
try self.lookup.put(key, self.entries.items.len - 1);
}
//
// REMOVAL
//
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn remove(self: *Self, key: K) bool {
const idx = self.lookup.get(key) orelse return false;
self.removeIndex(idx);
return true;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn removeLast(self: *Self) bool {
if (self.entries.items.len == 0) return false;
const last = self.entries.items.len - 1;
_ = self.lookup.remove(self.entries.items[last].key);
self.entries.swapRemove(last);
self.meta.swapRemove(last);
return true;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
fn removeIndex(self: *Self, idx: usize) void {
const last = self.entries.items.len - 1;
// --- UPDATE LOOKUP ---
if (idx != last) {
self.lookup.put(self.entries.items[last].key, idx) catch {};
}
_ = self.meta.swapRemove(idx);
_ = self.lookup.remove(self.entries.items[idx].key);
_ = self.entries.swapRemove(idx);
}
//
// LOOKUP
//
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn contains(self: Self, key: K) bool {
return self.lookup.contains(key);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn get(self: Self, key: K) ?*const V {
const idx = self.lookup.get(key) orelse return null;
return &self.entries.items[idx].value;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn getPtr(self: *Self, key: K) ?*V {
const idx = self.lookup.get(key) orelse return null;
return &self.entries.items[idx].value;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn getEntry(self: Self, key: K) ?*const Entry {
const idx = self.lookup.get(key) orelse return null;
return &self.entries.items[idx];
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn getMetadata(self: Self, key: K) ?*const Metadata {
const idx = self.lookup.get(key) orelse return null;
return &self.meta.items[idx];
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn getPtrMetadata(self: *Self, key: K) ?*Metadata {
const idx = self.lookup.get(key) orelse return null;
return &self.meta.items[idx];
}
//
// ITERATION
//
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn count(self: Self) usize {
return self.entries.items.len;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn values(self: Self) []const Entry {
return self.entries.items;
}
/// ----------------------------------------------------
/// Value, Metadata
/// ----------------------------------------------------
pub fn iterator(self: *Self) Iterator {
return .{ .registry = self, .index = 0 };
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub const Iterator = struct {
registry: *Self,
index: usize,
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn next(it: *Iterator) ?struct { value: *V, meta: *Metadata } {
if (it.index >= it.registry.entries.items.len) return null;
defer it.index += 1;
return .{
.value = &it.registry.entries.items[it.index].value,
.meta = &it.registry.meta.items[it.index],
};
}
};
//
// IDK
//
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn reloadAll(
self: *Self,
ctx: App.scheduler.Context,
) anyerror!void {
var it = self.iterator();
while (it.next()) |entry| {
if (entry.meta.reload) |reload| try reload(entry.value, ctx);
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn deinitReverse(self: *Self) void {
while (self.entries.items.len > 0) {
_ = self.removeLast();
}
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn clear(self: *Self) void {
self.meta.clearRetainingCapacity();
self.lookup.clearRetainingCapacity();
self.entries.clearRetainingCapacity();
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn clearAndFree(self: *Self) void {
self.meta.clearAndFree();
self.lookup.clearAndFree();
self.entries.clearAndFree();
}
};
}

3
src/template/root.zig Normal file
View file

@ -0,0 +1,3 @@
pub const Registry = @import("registry.zig").Registry;
pub const CameraPass = @import("camera_pass.zig");
pub const DrawCMD = @import("draw_cmd.zig");

1
src/vendors/sdl3/sdl.h vendored Normal file
View file

@ -0,0 +1 @@
#include <SDL3/SDL.h>

20
src/vendors/sdl3_glue/LICENSE.md vendored Normal file
View file

@ -0,0 +1,20 @@
MIT License
Copyright (c) 2022-2024 Élie Michel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

174
src/vendors/sdl3_glue/sdl3webgpu.c vendored Normal file
View file

@ -0,0 +1,174 @@
/**
* This is an extension of SDL3 for WebGPU, abstracting away the details of
* OS-specific operations.
*
* This file is part of the "Learn WebGPU for C++" book.
* https://eliemichel.github.io/LearnWebGPU
*
* Most of this code comes from the wgpu-native triangle example:
* https://github.com/gfx-rs/wgpu-native/blob/master/examples/triangle/main.c
*
* MIT License
* Copyright (c) 2022-2024 Elie Michel and the wgpu-native authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "sdl3webgpu.h"
#include "../wgpu/wgpu.h"
#if defined(SDL_PLATFORM_MACOS)
# include <Cocoa/Cocoa.h>
# include <Foundation/Foundation.h>
# include <QuartzCore/CAMetalLayer.h>
#elif defined(SDL_PLATFORM_IOS)
# include <UIKit/UIKit.h>
# include <Foundation/Foundation.h>
# include <QuartzCore/CAMetalLayer.h>
# include <Metal/Metal.h>
#elif defined(SDL_PLATFORM_WIN32)
# include <windows.h>
#endif
#include <SDL3/SDL.h>
WGPUSurface SDL_GetWGPUSurface(WGPUInstance instance, SDL_Window* window) {
SDL_PropertiesID props = SDL_GetWindowProperties(window);
#if defined(SDL_PLATFORM_MACOS)
{
id metal_layer = NULL;
NSWindow *ns_window = (__bridge NSWindow *)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL);
if (!ns_window) return NULL;
[ns_window.contentView setWantsLayer : YES];
metal_layer = [CAMetalLayer layer];
[ns_window.contentView setLayer : metal_layer];
WGPUSurfaceSourceMetalLayer fromMetalLayer;
fromMetalLayer.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
fromMetalLayer.chain.next = NULL;
fromMetalLayer.layer = metal_layer;
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromMetalLayer.chain;
surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN };
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
#elif defined(SDL_PLATFORM_IOS)
{
UIWindow *ui_window = (__bridge UIWindow *)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, NULL);
if (!uiwindow) return NULL;
UIView* ui_view = ui_window.rootViewController.view;
CAMetalLayer* metal_layer = [CAMetalLayer new];
metal_layer.opaque = true;
metal_layer.frame = ui_view.frame;
metal_layer.drawableSize = ui_view.frame.size;
[ui_view.layer addSublayer: metal_layer];
WGPUSurfaceSourceMetalLayer fromMetalLayer;
fromMetalLayer.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
fromMetalLayer.chain.next = NULL;
fromMetalLayer.layer = metal_layer;
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromMetalLayer.chain;
surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN };
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
#elif defined(SDL_PLATFORM_LINUX)
if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0) {
void *x11_display = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL);
uint64_t x11_window = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
if (!x11_display || !x11_window) return NULL;
WGPUSurfaceSourceXlibWindow fromXlibWindow;
fromXlibWindow.chain.sType = WGPUSType_SurfaceSourceXlibWindow;
fromXlibWindow.chain.next = NULL;
fromXlibWindow.display = x11_display;
fromXlibWindow.window = x11_window;
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromXlibWindow.chain;
surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN };
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
else if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) {
void *wayland_display = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL);
void *wayland_surface = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL);
if (!wayland_display || !wayland_surface) return NULL;
WGPUSurfaceSourceWaylandSurface fromWaylandSurface;
fromWaylandSurface.chain.sType = WGPUSType_SurfaceSourceWaylandSurface;
fromWaylandSurface.chain.next = NULL;
fromWaylandSurface.display = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL);
fromWaylandSurface.surface = wayland_surface;
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromWaylandSurface.chain;
surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN };
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
#elif defined(SDL_PLATFORM_WIN32)
{
HWND hwnd = (HWND)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
if (!hwnd) return NULL;
HINSTANCE hinstance = GetModuleHandle(NULL);
WGPUSurfaceSourceWindowsHWND fromWindowsHWND;
fromWindowsHWND.chain.sType = WGPUSType_SurfaceSourceWindowsHWND;
fromWindowsHWND.chain.next = NULL;
fromWindowsHWND.hinstance = hinstance;
fromWindowsHWND.hwnd = hwnd;
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromWindowsHWND.chain;
surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN };
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
#elif defined(__EMSCRIPTEN__)
{
# ifdef WEBGPU_BACKEND_EMDAWNWEBGPU
WGPUEmscriptenSurfaceSourceCanvasHTMLSelector fromCanvasHTMLSelector;
fromCanvasHTMLSelector.chain.sType = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector;
# else
WGPUSurfaceDescriptorFromCanvasHTMLSelector fromCanvasHTMLSelector;
fromCanvasHTMLSelector.chain.sType = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector;
# endif
fromCanvasHTMLSelector.chain.next = NULL;
fromCanvasHTMLSelector.selector = "canvas";
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromCanvasHTMLSelector.chain;
surfaceDescriptor.label = NULL;
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
#else
// TODO: See SDL_syswm.h for other possible enum values!
#error "Unsupported WGPU_TARGET"
#endif
}

49
src/vendors/sdl3_glue/sdl3webgpu.h vendored Normal file
View file

@ -0,0 +1,49 @@
/**
* This is an extension of SDL3 for WebGPU, abstracting away the details of
* OS-specific operations.
*
* This file is part of the "Learn WebGPU for C++" book.
* https://eliemichel.github.io/LearnWebGPU
*
* MIT License
* Copyright (c) 2022-2024 Elie Michel and the wgpu-native authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _sdl3_webgpu_h_
#define _sdl3_webgpu_h_
#include "../wgpu/wgpu.h"
#include <SDL3/SDL.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Get a WGPUSurface from a SDL3 window.
*/
WGPUSurface SDL_GetWGPUSurface(WGPUInstance instance, SDL_Window* window);
#ifdef __cplusplus
}
#endif
#endif // _sdl3_webgpu_h_

6768
src/vendors/wgpu/webgpu.h vendored Normal file

File diff suppressed because it is too large Load diff

1587
src/vendors/wgpu/wgpu.h vendored Normal file

File diff suppressed because it is too large Load diff