Skip to content

Commit

Permalink
Upgrade to WebGPU 0.19
Browse files Browse the repository at this point in the history
  • Loading branch information
chances committed Mar 23, 2024
1 parent 2a9e6a5 commit 4a4c156
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 44 deletions.
2 changes: 1 addition & 1 deletion dub.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"gfm:math": "~>8.0",
"wgpu-d": {
"repository": "git+https://github.com/chances/wgpu-d.git",
"version": "~master"
"version": "dfe9ed89edf818a025c5368130cd728f3d8801c4"
}
},
"subConfigurations": {
Expand Down
19 changes: 12 additions & 7 deletions source/game.d
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ abstract class Game {
import teraflop.ecs : System, SystemGenerator, World;
import teraflop.input : Input, InputDevice, InputEvent;
import teraflop.time : Time;
import wgpu.api : Adapter, Device, SwapChain;
import wgpu.api : Adapter, Device, Instance;

private string name_;
private bool active_;
private auto time_ = Time.zero;
private bool limitFrameRate = true;
private int desiredFrameRateHertz_ = 60;

private Instance instance;
private Adapter adapter;
private Device device;
private Window[] windows_;
Expand Down Expand Up @@ -115,20 +116,21 @@ abstract class Game {
import std.algorithm.searching : all;
import std.datetime.stopwatch : AutoStart, StopWatch;
import teraflop.platform.window : initGlfw, terminateGlfw;
import wgpu.api : Instance, PowerPreference;
import wgpu.api : PowerPreference;

// Setup main window
if (!initGlfw()) {
return;
}
scope(exit) terminateGlfw();

windows_ ~= _mainWindow = new Window(name);
windows_ ~= _mainWindow = new Window(instance, name);
if (!_mainWindow.valid) return;

// Setup root graphics resources
// Setup graphics resources
this.instance = Instance.create();
// TODO: Select `PowerPreference.lowPower` on laptops and whatnot
auto adapter = Instance.requestAdapter(_mainWindow.surface, PowerPreference.highPerformance);
this.adapter = instance.requestAdapter(_mainWindow.surface, PowerPreference.highPerformance);
assert(adapter.ready);
device = adapter.requestDevice(adapter.limits);
assert(device.ready);
Expand Down Expand Up @@ -238,8 +240,11 @@ abstract class Game {
import std.typecons : Yes;
import teraflop.graphics.color : Color;
import wgpu.api : RenderPass;
import wgpu.utils : wrap;

auto frame = _mainWindow.swapChain.getNextTexture();
auto surface = _mainWindow.surface.getCurrentTexture();
auto surfaceTexture = surface.texture.wrap(_mainWindow.surface.descriptor);
auto frame = surfaceTexture.defaultView;
// TODO: Add `wgpu.api.TextureView.valid` property
// TODO: assert(frame.valid !is null, "Could not get next swap chain texture");
auto encoder = device.createCommandEncoder(name);
Expand All @@ -256,7 +261,7 @@ abstract class Game {

auto commandBuffer = encoder.finish();
device.queue.submit(commandBuffer);
_mainWindow.swapChain.present();
_mainWindow.surface.present();

// Force wait for a frame to render and pump callbacks
device.poll(Yes.forceWait);
Expand Down
14 changes: 5 additions & 9 deletions source/graphics/color.d
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
module teraflop.graphics.color;

import std.conv : to;
import wgpu.bindings : WGPUColor;
static import teraflop.math;
import wgpuApi = wgpu.api;

///
struct Color {
Expand Down Expand Up @@ -86,13 +86,9 @@ struct Color {
);
}

package (teraflop) WGPUColor wgpu() const @property {
return WGPUColor(
r / ubyte.max.to!double,
g / ubyte.max.to!double,
b / ubyte.max.to!double,
a / ubyte.max.to!double
);
package (teraflop) wgpuApi.Color wgpu() const @property {
auto color = this.vec4f;
return wgpuApi.Color(color.r, color.g, color.b, color.a);
}

/// Adjust a `Color`s alpha channel, setting it to the given percentage.
Expand Down Expand Up @@ -135,7 +131,7 @@ unittest {

// Cornflower Blue #6495ed
const cornflowerBlue = Color(0x64, 0x95, 0xED);
const expected = WGPUColor(0.392, 0.584, 0.929, 1);
const expected = wgpuApi.Color(0.392, 0.584, 0.929, 1);

assert(cornflowerBlue.wgpu.r.isClose(expected.r, 0.00075), text(cornflowerBlue.wgpu.r));
assert(cornflowerBlue.wgpu.g.isClose(expected.g, 0.00075), text(cornflowerBlue.wgpu.g));
Expand Down
11 changes: 7 additions & 4 deletions source/platform/wgpu.d
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
module teraflop.platform.wgpu;

import bindbc.glfw;
import wgpu.api : Device, Surface, SwapChain, SwapChainDescriptor;
import wgpu.api : Device, Instance, Surface;

///
Surface createPlatformSurface(GLFWwindow* window, string label = null) {
Surface createPlatformSurface(Instance instance, GLFWwindow* window, string label = null) {
import std.conv : to;

version (linux) {
auto display = glfwGetX11Display();
auto x11Window = glfwGetX11Window(window);
if (display != null && x11Window > 0)
return Surface.fromXlib(display, x11Window.to!uint, label);
return Surface.fromXlib(instance, display, x11Window.to!uint, label);
} else version (OSX) {
auto cocoaWindow = cast(NSWindow) glfwGetCocoaWindow(window);
cocoaWindow.contentView.wantsLayer = true;
assert(cocoaWindow.contentView.wantsLayer);
// FIXME: LDC doesn't support Objective-C linkage
cocoaWindow.contentView.layer = CAMetalLayer.layer;
return Surface.fromMetalLayer(cast(void*) cocoaWindow.contentView.layer, label);
return Surface.fromMetalLayer(instance, cast(void*) cocoaWindow.contentView.layer, label);
} else version (Windows) {
return Surface.fromWindowsHwnd(instance, null, glfwGetWin32Window(window), label);
}

assert(0, "Unsupported target platform!");
Expand Down
50 changes: 27 additions & 23 deletions source/platform/window.d
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,16 @@ private int lastWindowId = 0;
class Window : InputNode, Resource {
import teraflop.input : KeyboardKey, MouseButton;
import teraflop.math : Size, vec2d;
import wgpu.api : Adapter, Device, Surface, SwapChain;
import wgpu.api : Adapter, Device, Instance, Surface, TextureFormat;

/// Window identifier
const int id;

private GLFWwindow* window;
private Device device_;
private Surface surface_;
private SwapChain swapChain_;
private TextureFormat swapChainFormat_;
private const EventLoop eventLoop_;
private bool valid_ = false;
private string title_;
private WindowData data;

Expand All @@ -43,7 +42,7 @@ class Window : InputNode, Resource {
/// width = Initial width of the Window
/// height = Initial height of the Window
/// initiallyFocused = Whether the window will be given input focus when created
this(string title, int width = 800, int height = 600, bool initiallyFocused = true) {
this(Instance instance, string title, int width = 800, int height = 600, bool initiallyFocused = true) {
import teraflop.async : createEventLoop;

id = lastWindowId += 1;
Expand All @@ -57,8 +56,7 @@ class Window : InputNode, Resource {
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // Graphics are handled by wgpu

window = glfwCreateWindow(width, height, toStringz(title), null, null);
valid_ = window !is null;
if (!valid_) {
if (!valid) {
errorCallback(0, toStringz("Failed to initialize a new GLFW Window"));
return;
}
Expand All @@ -80,8 +78,7 @@ class Window : InputNode, Resource {
// TODO: https://www.glfw.org/docs/latest/input_guide.html#joystick and https://www.glfw.org/docs/latest/input_guide.html#gamepad

// Initialize GPU surface and swap chain descriptor
surface_ = createPlatformSurface(window);
valid_ = surface_.id != null;
surface_ = createPlatformSurface(instance, window);
if (!valid) {
glfwDestroyWindow(window);
errorCallback(0, toStringz("Failed to initialize a new GPU surface"));
Expand Down Expand Up @@ -115,11 +112,14 @@ class Window : InputNode, Resource {
title_ = value;
}

/// Whether the native window handle is valid.
/// Whether the native window handle and backing GPU surface is valid.
///
/// May be `false` if Window initialization failed .
/// May be `false` if Window initialization failed.
bool valid() @property const {
return valid_;
import wgpu.utils : valid;
if (window is null) return false;
if (surface is null) return false;
return surface_.valid;
}

///
Expand Down Expand Up @@ -224,12 +224,18 @@ class Window : InputNode, Resource {
package (teraflop) Surface surface() @trusted @property const {
return cast(Surface) surface_;
}
package (teraflop) TextureFormat surfaceFormat() @trusted @property const {
import std.conv : asOriginalType;
// TODO: Abstract this check back into a `Surface.valid` parameter
auto config = surface.config;
assert(config !is null, "Cannot create texture descriptor: This surface is not configured.");
if (config is null) throw new Exception("Cannot create texture descriptor: This surface is not configured.");

return config.format.asOriginalType.to!TextureFormat;
}
package (teraflop) bool dirty() @property const {
return data.dirty;
}
package (teraflop) SwapChain swapChain() @trusted @property const {
return cast(SwapChain) swapChain_;
}

bool isKeyDown(KeyboardKey key) @property const {
return (key in data.keyPressed) !is null ? data.keyPressed[key] : false;
Expand Down Expand Up @@ -285,17 +291,15 @@ class Window : InputNode, Resource {

/// See_Also: `Resource`
void initialize(Adapter adapter, Device device) {
import wgpu.api : PresentMode, TextureUsage;
import wgpu.api : CompositeAlphaMode, PresentMode, TextureUsage;

this.device_ = device;

auto swapChainFormat = surface.preferredFormat(adapter);
swapChain_ = device.createSwapChain(
surface, surfaceSize.width, surfaceSize.height, swapChainFormat,
// TODO: Remove this redundant texture usage parameter
this.swapChainFormat_ = surface.preferredFormat(adapter);
surface.configure(
device, surfaceSize.width, surfaceSize.height, surfaceFormat,
TextureUsage.renderAttachment,
PresentMode.fifo,
title
PresentMode.fifo, CompositeAlphaMode.auto_
);
}

Expand All @@ -312,7 +316,7 @@ class Window : InputNode, Resource {
package (teraflop) void update() {
if (glfwWindowShouldClose(window)) {
glfwDestroyWindow(window);
valid_ = false;
window = null;
return;
}

Expand All @@ -330,7 +334,7 @@ class Window : InputNode, Resource {

// Force wait to flush GPU queue and pump callbacks
device_.poll(Yes.forceWait);
swapChain_ = swapChain_.resize(device_, surfaceSize.width, surfaceSize.height);
surface.resize(surfaceSize.width, surfaceSize.height);
data.dirty = false;
}

Expand Down

0 comments on commit 4a4c156

Please sign in to comment.