add higher-level app library
Fixes hexops/mach#190 Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
38095942d9
commit
49f80de6ab
7 changed files with 586 additions and 10 deletions
70
build.zig
70
build.zig
|
|
@ -1,23 +1,73 @@
|
||||||
const Builder = @import("std").build.Builder;
|
const std = @import("std");
|
||||||
|
const gpu = @import("gpu/build.zig");
|
||||||
|
const gpu_dawn = @import("gpu-dawn/build.zig");
|
||||||
const glfw = @import("glfw/build.zig");
|
const glfw = @import("glfw/build.zig");
|
||||||
|
|
||||||
pub fn build(b: *Builder) void {
|
pub fn build(b: *std.build.Builder) void {
|
||||||
const mode = b.standardReleaseOptions();
|
const mode = b.standardReleaseOptions();
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
|
|
||||||
const lib = b.addStaticLibrary("engine", "src/main.zig");
|
const gpu_dawn_options = gpu_dawn.Options{
|
||||||
lib.setBuildMode(mode);
|
.from_source = b.option(bool, "dawn-from-source", "Build Dawn from source") orelse false,
|
||||||
lib.setTarget(target);
|
};
|
||||||
lib.addPackagePath("glfw", "glfw/src/main.zig");
|
const options = Options {.gpu_dawn_options = gpu_dawn_options};
|
||||||
glfw.link(b, lib, .{});
|
|
||||||
lib.install();
|
|
||||||
|
|
||||||
const main_tests = b.addTest("src/main.zig");
|
const main_tests = b.addTest("src/main.zig");
|
||||||
main_tests.setBuildMode(mode);
|
main_tests.setBuildMode(mode);
|
||||||
main_tests.setTarget(target);
|
main_tests.setTarget(target);
|
||||||
main_tests.addPackagePath("glfw", "glfw/src/main.zig");
|
main_tests.addPackage(pkg);
|
||||||
glfw.link(b, main_tests, .{});
|
main_tests.addPackage(gpu.pkg);
|
||||||
|
link(b, main_tests, options);
|
||||||
|
|
||||||
const test_step = b.step("test", "Run library tests");
|
const test_step = b.step("test", "Run library tests");
|
||||||
test_step.dependOn(&main_tests.step);
|
test_step.dependOn(&main_tests.step);
|
||||||
|
|
||||||
|
const example = b.addExecutable("hello-triangle", "examples/main.zig");
|
||||||
|
example.setTarget(target);
|
||||||
|
example.setBuildMode(mode);
|
||||||
|
example.addPackage(pkg);
|
||||||
|
example.addPackage(gpu.pkg);
|
||||||
|
link(b, example, options);
|
||||||
|
example.install();
|
||||||
|
|
||||||
|
const example_run_cmd = example.run();
|
||||||
|
example_run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
const example_run_step = b.step("run-example", "Run the example");
|
||||||
|
example_run_step.dependOn(&example_run_cmd.step);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Options = struct {
|
||||||
|
glfw_options: glfw.Options = .{},
|
||||||
|
gpu_dawn_options: gpu_dawn.Options = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const pkg = .{
|
||||||
|
.name = "mach",
|
||||||
|
.path = .{ .path = thisDir() ++ "/src/main.zig" },
|
||||||
|
.dependencies = &.{ gpu.pkg, glfw.pkg },
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn link(b: *std.build.Builder, step: *std.build.LibExeObjStep, options: Options) void {
|
||||||
|
const gpu_options = gpu.Options{
|
||||||
|
.glfw_options = @bitCast(@import("gpu/libs/mach-glfw/build.zig").Options, options.glfw_options),
|
||||||
|
.gpu_dawn_options = @bitCast(@import("gpu/libs/mach-gpu-dawn/build.zig").Options, options.gpu_dawn_options),
|
||||||
|
};
|
||||||
|
|
||||||
|
const main_abs = std.fs.path.join(b.allocator, &.{ thisDir(), "src/main.zig" }) catch unreachable;
|
||||||
|
const lib = b.addStaticLibrary("mach", main_abs);
|
||||||
|
lib.setBuildMode(step.build_mode);
|
||||||
|
lib.setTarget(step.target);
|
||||||
|
lib.addPackage(gpu.pkg);
|
||||||
|
lib.addPackage(glfw.pkg);
|
||||||
|
|
||||||
|
glfw.link(b, lib, options.glfw_options);
|
||||||
|
gpu.link(b, lib, gpu_options);
|
||||||
|
lib.install();
|
||||||
|
|
||||||
|
glfw.link(b, step, options.glfw_options);
|
||||||
|
gpu.link(b, step, gpu_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thisDir() []const u8 {
|
||||||
|
return std.fs.path.dirname(@src().file) orelse ".";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
examples/frag.wgsl
Normal file
3
examples/frag.wgsl
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
@stage(fragment) fn main() -> @location(0) vec4<f32> {
|
||||||
|
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
114
examples/main.zig
Normal file
114
examples/main.zig
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const mach = @import("mach");
|
||||||
|
const gpu = @import("gpu");
|
||||||
|
|
||||||
|
const App = mach.App(*FrameParams, .{});
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
var allocator = gpa.allocator();
|
||||||
|
|
||||||
|
const ctx = try allocator.create(FrameParams);
|
||||||
|
var app = try App.init(allocator, ctx, .{});
|
||||||
|
|
||||||
|
const vs_module = app.device.createShaderModule(&.{
|
||||||
|
.label = "my vertex shader",
|
||||||
|
.code = .{ .wgsl = @embedFile("vert.wgsl") },
|
||||||
|
});
|
||||||
|
|
||||||
|
const fs_module = app.device.createShaderModule(&.{
|
||||||
|
.label = "my fragment shader",
|
||||||
|
.code = .{ .wgsl = @embedFile("frag.wgsl") },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fragment state
|
||||||
|
const blend = gpu.BlendState{
|
||||||
|
.color = .{
|
||||||
|
.operation = .add,
|
||||||
|
.src_factor = .one,
|
||||||
|
.dst_factor = .one,
|
||||||
|
},
|
||||||
|
.alpha = .{
|
||||||
|
.operation = .add,
|
||||||
|
.src_factor = .one,
|
||||||
|
.dst_factor = .one,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const color_target = gpu.ColorTargetState{
|
||||||
|
.format = app.swap_chain_format,
|
||||||
|
.blend = &blend,
|
||||||
|
.write_mask = .all,
|
||||||
|
};
|
||||||
|
const fragment = gpu.FragmentState{
|
||||||
|
.module = fs_module,
|
||||||
|
.entry_point = "main",
|
||||||
|
.targets = &.{color_target},
|
||||||
|
.constants = null,
|
||||||
|
};
|
||||||
|
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
|
||||||
|
.fragment = &fragment,
|
||||||
|
.layout = null,
|
||||||
|
.depth_stencil = null,
|
||||||
|
.vertex = .{
|
||||||
|
.module = vs_module,
|
||||||
|
.entry_point = "main",
|
||||||
|
.buffers = null,
|
||||||
|
},
|
||||||
|
.multisample = .{
|
||||||
|
.count = 1,
|
||||||
|
.mask = 0xFFFFFFFF,
|
||||||
|
.alpha_to_coverage_enabled = false,
|
||||||
|
},
|
||||||
|
.primitive = .{
|
||||||
|
.front_face = .ccw,
|
||||||
|
.cull_mode = .none,
|
||||||
|
.topology = .triangle_list,
|
||||||
|
.strip_index_format = .none,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.* = FrameParams{
|
||||||
|
.pipeline = app.device.createRenderPipeline(&pipeline_descriptor),
|
||||||
|
.queue = app.device.getQueue(),
|
||||||
|
};
|
||||||
|
|
||||||
|
vs_module.release();
|
||||||
|
fs_module.release();
|
||||||
|
|
||||||
|
try app.run(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FrameParams = struct {
|
||||||
|
pipeline: gpu.RenderPipeline,
|
||||||
|
queue: gpu.Queue,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn frame(app: *App, params: *FrameParams) !void {
|
||||||
|
const back_buffer_view = app.swap_chain.?.getCurrentTextureView();
|
||||||
|
const color_attachment = gpu.RenderPassColorAttachment{
|
||||||
|
.view = back_buffer_view,
|
||||||
|
.resolve_target = null,
|
||||||
|
.clear_value = std.mem.zeroes(gpu.Color),
|
||||||
|
.load_op = .clear,
|
||||||
|
.store_op = .store,
|
||||||
|
};
|
||||||
|
|
||||||
|
const encoder = app.device.createCommandEncoder(null);
|
||||||
|
const render_pass_info = gpu.RenderPassEncoder.Descriptor{
|
||||||
|
.color_attachments = &.{color_attachment},
|
||||||
|
.depth_stencil_attachment = null,
|
||||||
|
};
|
||||||
|
const pass = encoder.beginRenderPass(&render_pass_info);
|
||||||
|
pass.setPipeline(params.pipeline);
|
||||||
|
pass.draw(3, 1, 0, 0);
|
||||||
|
pass.end();
|
||||||
|
pass.release();
|
||||||
|
|
||||||
|
var command = encoder.finish(null);
|
||||||
|
encoder.release();
|
||||||
|
|
||||||
|
params.queue.submit(&.{command});
|
||||||
|
command.release();
|
||||||
|
app.swap_chain.?.present();
|
||||||
|
back_buffer_view.release();
|
||||||
|
}
|
||||||
10
examples/vert.wgsl
Normal file
10
examples/vert.wgsl
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
@stage(vertex) fn main(
|
||||||
|
@builtin(vertex_index) VertexIndex : u32
|
||||||
|
) -> @builtin(position) vec4<f32> {
|
||||||
|
var pos = array<vec2<f32>, 3>(
|
||||||
|
vec2<f32>( 0.0, 0.5),
|
||||||
|
vec2<f32>(-0.5, -0.5),
|
||||||
|
vec2<f32>( 0.5, -0.5)
|
||||||
|
);
|
||||||
|
return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
|
||||||
|
}
|
||||||
5
src/c.zig
Normal file
5
src/c.zig
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
pub const c = @cImport({
|
||||||
|
@cInclude("dawn/webgpu.h");
|
||||||
|
@cInclude("dawn/dawn_proc.h");
|
||||||
|
@cInclude("dawn_native_mach.h");
|
||||||
|
});
|
||||||
226
src/main.zig
226
src/main.zig
|
|
@ -2,7 +2,233 @@ const std = @import("std");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
const glfw = @import("glfw");
|
const glfw = @import("glfw");
|
||||||
|
const gpu = @import("gpu");
|
||||||
|
const util = @import("util.zig");
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
|
/// For now, this contains nothing. In the future, this will include application configuration that
|
||||||
|
/// can only be specified at compile-time.
|
||||||
|
pub const AppConfig = struct {};
|
||||||
|
|
||||||
|
/// Application options that can be configured at init time.
|
||||||
|
pub const Options = struct {
|
||||||
|
/// The title of the window.
|
||||||
|
title: [*:0]const u8 = "Mach engine",
|
||||||
|
|
||||||
|
/// The width of the window.
|
||||||
|
width: u32 = 640,
|
||||||
|
|
||||||
|
/// The height of the window.
|
||||||
|
height: u32 = 480,
|
||||||
|
|
||||||
|
/// GPU features required by the application.
|
||||||
|
required_features: ?[]gpu.Feature = null,
|
||||||
|
|
||||||
|
/// GPU limits required by the application.
|
||||||
|
required_limits: ?gpu.Limits = null,
|
||||||
|
|
||||||
|
/// Whether the application has a preference for low power or high performance GPU.
|
||||||
|
power_preference: gpu.PowerPreference = .none,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A Mach application.
|
||||||
|
///
|
||||||
|
/// The Context type is your own data type which can later be accessed via app.context from within
|
||||||
|
/// the frame function you pass to run().
|
||||||
|
pub fn App(comptime Context: type, comptime config: AppConfig) type {
|
||||||
|
_ = config;
|
||||||
|
return struct {
|
||||||
|
context: Context,
|
||||||
|
device: gpu.Device,
|
||||||
|
window: glfw.Window,
|
||||||
|
backend_type: gpu.Adapter.BackendType,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
swap_chain: ?gpu.SwapChain,
|
||||||
|
swap_chain_format: gpu.Texture.Format,
|
||||||
|
|
||||||
|
// Internals
|
||||||
|
native_instance: gpu.NativeInstance,
|
||||||
|
surface: ?gpu.Surface,
|
||||||
|
current_desc: gpu.SwapChain.Descriptor,
|
||||||
|
target_desc: gpu.SwapChain.Descriptor,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, context: Context, options: Options) !Self {
|
||||||
|
const backend_type = try util.detectBackendType(allocator);
|
||||||
|
|
||||||
|
try glfw.init(.{});
|
||||||
|
|
||||||
|
// Create the test window and discover adapters using it (esp. for OpenGL)
|
||||||
|
var hints = util.glfwWindowHintsForBackend(backend_type);
|
||||||
|
hints.cocoa_retina_framebuffer = true;
|
||||||
|
const window = try glfw.Window.create(
|
||||||
|
options.width,
|
||||||
|
options.height,
|
||||||
|
options.title,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
hints,
|
||||||
|
);
|
||||||
|
|
||||||
|
const backend_procs = c.machDawnNativeGetProcs();
|
||||||
|
c.dawnProcSetProcs(backend_procs);
|
||||||
|
|
||||||
|
const instance = c.machDawnNativeInstance_init();
|
||||||
|
var native_instance = gpu.NativeInstance.wrap(c.machDawnNativeInstance_get(instance).?);
|
||||||
|
|
||||||
|
// Discover e.g. OpenGL adapters.
|
||||||
|
try util.discoverAdapters(instance, window, backend_type);
|
||||||
|
|
||||||
|
// Request an adapter.
|
||||||
|
//
|
||||||
|
// TODO: It would be nice if we could use gpu_interface.waitForAdapter here, however the webgpu.h
|
||||||
|
// API does not yet have a way to specify what type of backend you want (vulkan, opengl, etc.)
|
||||||
|
// In theory, I suppose we shouldn't need to and Dawn should just pick the best adapter - but in
|
||||||
|
// practice if Vulkan is not supported today waitForAdapter/requestAdapter merely generates an error.
|
||||||
|
//
|
||||||
|
// const gpu_interface = native_instance.interface();
|
||||||
|
// const backend_adapter = switch (gpu_interface.waitForAdapter(&.{
|
||||||
|
// .power_preference = .high_performance,
|
||||||
|
// })) {
|
||||||
|
// .adapter => |v| v,
|
||||||
|
// .err => |err| {
|
||||||
|
// std.debug.print("mach: failed to get adapter: error={} {s}\n", .{ err.code, err.message });
|
||||||
|
// std.process.exit(1);
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
const adapters = c.machDawnNativeInstance_getAdapters(instance);
|
||||||
|
var dawn_adapter: ?c.MachDawnNativeAdapter = null;
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < c.machDawnNativeAdapters_length(adapters)) : (i += 1) {
|
||||||
|
const adapter = c.machDawnNativeAdapters_index(adapters, i);
|
||||||
|
const properties = c.machDawnNativeAdapter_getProperties(adapter);
|
||||||
|
const found_backend_type = @intToEnum(gpu.Adapter.BackendType, c.machDawnNativeAdapterProperties_getBackendType(properties));
|
||||||
|
if (found_backend_type == backend_type) {
|
||||||
|
dawn_adapter = adapter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dawn_adapter == null) {
|
||||||
|
std.debug.print("mach: no matching adapter found for {s}", .{@tagName(backend_type)});
|
||||||
|
std.debug.print("-> maybe try GPU_BACKEND=opengl ?\n", .{});
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
std.debug.assert(dawn_adapter != null);
|
||||||
|
const backend_adapter = gpu.NativeInstance.fromWGPUAdapter(c.machDawnNativeAdapter_get(dawn_adapter.?).?);
|
||||||
|
|
||||||
|
// Print which adapter we are going to use.
|
||||||
|
const props = backend_adapter.properties;
|
||||||
|
std.debug.print("mach: found {s} backend on {s} adapter: {s}, {s}\n", .{
|
||||||
|
gpu.Adapter.backendTypeName(props.backend_type),
|
||||||
|
gpu.Adapter.typeName(props.adapter_type),
|
||||||
|
props.name,
|
||||||
|
props.driver_description,
|
||||||
|
});
|
||||||
|
|
||||||
|
const device = switch (backend_adapter.waitForDevice(&.{
|
||||||
|
.required_features = options.required_features,
|
||||||
|
.required_limits = options.required_limits,
|
||||||
|
})) {
|
||||||
|
.device => |v| v,
|
||||||
|
.err => |err| {
|
||||||
|
// TODO: return a proper error type
|
||||||
|
std.debug.print("mach: failed to get device: error={} {s}\n", .{ err.code, err.message });
|
||||||
|
std.process.exit(1);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var framebuffer_size = try window.getFramebufferSize();
|
||||||
|
|
||||||
|
// If targeting OpenGL, we can't use the newer WGPUSurface API. Instead, we need to use the
|
||||||
|
// older Dawn-specific API. https://bugs.chromium.org/p/dawn/issues/detail?id=269&q=surface&can=2
|
||||||
|
const use_legacy_api = backend_type == .opengl or backend_type == .opengles;
|
||||||
|
var descriptor: gpu.SwapChain.Descriptor = undefined;
|
||||||
|
var swap_chain: ?gpu.SwapChain = null;
|
||||||
|
var swap_chain_format: gpu.Texture.Format = undefined;
|
||||||
|
var surface: ?gpu.Surface = null;
|
||||||
|
if (!use_legacy_api) {
|
||||||
|
swap_chain_format = .bgra8_unorm;
|
||||||
|
descriptor = .{
|
||||||
|
.label = "basic swap chain",
|
||||||
|
.usage = .render_attachment,
|
||||||
|
.format = swap_chain_format,
|
||||||
|
.width = framebuffer_size.width,
|
||||||
|
.height = framebuffer_size.height,
|
||||||
|
.present_mode = .fifo,
|
||||||
|
.implementation = 0,
|
||||||
|
};
|
||||||
|
surface = util.createSurfaceForWindow(
|
||||||
|
&native_instance,
|
||||||
|
window,
|
||||||
|
comptime util.detectGLFWOptions(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const binding = c.machUtilsCreateBinding(@enumToInt(backend_type), @ptrCast(*c.GLFWwindow, window.handle), @ptrCast(c.WGPUDevice, device.ptr));
|
||||||
|
if (binding == null) {
|
||||||
|
@panic("failed to create Dawn backend binding");
|
||||||
|
}
|
||||||
|
descriptor = std.mem.zeroes(gpu.SwapChain.Descriptor);
|
||||||
|
descriptor.implementation = c.machUtilsBackendBinding_getSwapChainImplementation(binding);
|
||||||
|
swap_chain = device.nativeCreateSwapChain(null, &descriptor);
|
||||||
|
|
||||||
|
swap_chain_format = @intToEnum(gpu.Texture.Format, @intCast(u32, c.machUtilsBackendBinding_getPreferredSwapChainTextureFormat(binding)));
|
||||||
|
swap_chain.?.configure(
|
||||||
|
swap_chain_format,
|
||||||
|
.render_attachment,
|
||||||
|
framebuffer_size.width,
|
||||||
|
framebuffer_size.height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
device.setUncapturedErrorCallback(&util.printUnhandledErrorCallback);
|
||||||
|
return Self{
|
||||||
|
.context = context,
|
||||||
|
.device = device,
|
||||||
|
.window = window,
|
||||||
|
.backend_type = backend_type,
|
||||||
|
.allocator = allocator,
|
||||||
|
|
||||||
|
.native_instance = native_instance,
|
||||||
|
.surface = surface,
|
||||||
|
.swap_chain = swap_chain,
|
||||||
|
.swap_chain_format = swap_chain_format,
|
||||||
|
.current_desc = descriptor,
|
||||||
|
.target_desc = descriptor,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const FrameFunc = fn (app: *Self, ctx: Context) error{OutOfMemory}!void;
|
||||||
|
|
||||||
|
pub fn run(app: *Self, frame: FrameFunc) !void {
|
||||||
|
while (!app.window.shouldClose()) {
|
||||||
|
try glfw.pollEvents();
|
||||||
|
|
||||||
|
var framebuffer_size = try app.window.getFramebufferSize();
|
||||||
|
app.target_desc.width = framebuffer_size.width;
|
||||||
|
app.target_desc.height = framebuffer_size.height;
|
||||||
|
|
||||||
|
if (app.swap_chain == null or !app.current_desc.equal(&app.target_desc)) {
|
||||||
|
const use_legacy_api = app.surface == null;
|
||||||
|
if (!use_legacy_api) {
|
||||||
|
app.swap_chain = app.device.nativeCreateSwapChain(app.surface, &app.target_desc);
|
||||||
|
} else app.swap_chain.?.configure(
|
||||||
|
app.swap_chain_format,
|
||||||
|
.render_attachment,
|
||||||
|
app.target_desc.width,
|
||||||
|
app.target_desc.height,
|
||||||
|
);
|
||||||
|
app.current_desc = app.target_desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
try frame(app, app.context);
|
||||||
|
std.time.sleep(16 * std.time.ns_per_ms); // TODO: this is very naive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
test "glfw_basic" {
|
test "glfw_basic" {
|
||||||
|
_ = Options;
|
||||||
|
_ = App;
|
||||||
glfw.basicTest() catch unreachable;
|
glfw.basicTest() catch unreachable;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
168
src/util.zig
Normal file
168
src/util.zig
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const glfw = @import("glfw");
|
||||||
|
const gpu = @import("gpu");
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
const objc = @cImport({
|
||||||
|
@cInclude("objc/message.h");
|
||||||
|
});
|
||||||
|
|
||||||
|
fn printUnhandledError(_: void, typ: gpu.ErrorType, message: [*:0]const u8) void {
|
||||||
|
switch (typ) {
|
||||||
|
.validation => std.debug.print("gpu: validation error: {s}\n", .{message}),
|
||||||
|
.out_of_memory => std.debug.print("gpu: out of memory: {s}\n", .{message}),
|
||||||
|
.device_lost => std.debug.print("gpu: device lost: {s}\n", .{message}),
|
||||||
|
.unknown => std.debug.print("gpu: unknown error: {s}\n", .{message}),
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
std.os.exit(1);
|
||||||
|
}
|
||||||
|
pub var printUnhandledErrorCallback = gpu.ErrorCallback.init(void, {}, printUnhandledError);
|
||||||
|
|
||||||
|
fn getEnvVarOwned(allocator: std.mem.Allocator, key: []const u8) error{ OutOfMemory, InvalidUtf8 }!?[]u8 {
|
||||||
|
return std.process.getEnvVarOwned(allocator, key) catch |err| switch (err) {
|
||||||
|
error.EnvironmentVariableNotFound => @as(?[]u8, null),
|
||||||
|
else => |e| e,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn detectBackendType(allocator: std.mem.Allocator) !gpu.Adapter.BackendType {
|
||||||
|
const GPU_BACKEND = try getEnvVarOwned(allocator, "GPU_BACKEND");
|
||||||
|
if (GPU_BACKEND) |backend| {
|
||||||
|
defer allocator.free(backend);
|
||||||
|
if (std.ascii.eqlIgnoreCase(backend, "opengl")) return .opengl;
|
||||||
|
if (std.ascii.eqlIgnoreCase(backend, "opengles")) return .opengles;
|
||||||
|
if (std.ascii.eqlIgnoreCase(backend, "d3d11")) return .d3d11;
|
||||||
|
if (std.ascii.eqlIgnoreCase(backend, "d3d12")) return .d3d12;
|
||||||
|
if (std.ascii.eqlIgnoreCase(backend, "metal")) return .metal;
|
||||||
|
if (std.ascii.eqlIgnoreCase(backend, "null")) return .nul;
|
||||||
|
if (std.ascii.eqlIgnoreCase(backend, "vulkan")) return .vulkan;
|
||||||
|
@panic("unknown GPU_BACKEND type");
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = @import("builtin").target;
|
||||||
|
if (target.isDarwin()) return .metal;
|
||||||
|
if (target.os.tag == .windows) return .d3d12;
|
||||||
|
return .vulkan;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn glfwWindowHintsForBackend(backend: gpu.Adapter.BackendType) glfw.Window.Hints {
|
||||||
|
return switch (backend) {
|
||||||
|
.opengl => .{
|
||||||
|
// Ask for OpenGL 4.4 which is what the GL backend requires for compute shaders and
|
||||||
|
// texture views.
|
||||||
|
.context_version_major = 4,
|
||||||
|
.context_version_minor = 4,
|
||||||
|
.opengl_forward_compat = true,
|
||||||
|
.opengl_profile = .opengl_core_profile,
|
||||||
|
},
|
||||||
|
.opengles => .{
|
||||||
|
.context_version_major = 3,
|
||||||
|
.context_version_minor = 1,
|
||||||
|
.client_api = .opengl_es_api,
|
||||||
|
.context_creation_api = .egl_context_api,
|
||||||
|
},
|
||||||
|
else => .{
|
||||||
|
// Without this GLFW will initialize a GL context on the window, which prevents using
|
||||||
|
// the window with other APIs (by crashing in weird ways).
|
||||||
|
.client_api = .no_api,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discoverAdapters(instance: c.MachDawnNativeInstance, window: glfw.Window, typ: gpu.Adapter.BackendType) !void {
|
||||||
|
switch (typ) {
|
||||||
|
.opengl => {
|
||||||
|
try glfw.makeContextCurrent(window);
|
||||||
|
const adapter_options = c.MachDawnNativeAdapterDiscoveryOptions_OpenGL{
|
||||||
|
.getProc = @ptrCast(fn ([*c]const u8) callconv(.C) ?*anyopaque, glfw.getProcAddress),
|
||||||
|
};
|
||||||
|
_ = c.machDawnNativeInstance_discoverAdapters(instance, @enumToInt(typ), &adapter_options);
|
||||||
|
},
|
||||||
|
.opengles => {
|
||||||
|
try glfw.makeContextCurrent(window);
|
||||||
|
const adapter_options = c.MachDawnNativeAdapterDiscoveryOptions_OpenGLES{
|
||||||
|
.getProc = @ptrCast(fn ([*c]const u8) callconv(.C) ?*anyopaque, glfw.getProcAddress),
|
||||||
|
};
|
||||||
|
_ = c.machDawnNativeInstance_discoverAdapters(instance, @enumToInt(typ), &adapter_options);
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
c.machDawnNativeInstance_discoverDefaultAdapters(instance);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn detectGLFWOptions() glfw.BackendOptions {
|
||||||
|
const target = @import("builtin").target;
|
||||||
|
if (target.isDarwin()) return .{ .cocoa = true };
|
||||||
|
return switch (target.os.tag) {
|
||||||
|
.windows => .{ .win32 = true },
|
||||||
|
.linux => .{ .x11 = true },
|
||||||
|
else => .{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createSurfaceForWindow(
|
||||||
|
native_instance: *const gpu.NativeInstance,
|
||||||
|
window: glfw.Window,
|
||||||
|
comptime glfw_options: glfw.BackendOptions,
|
||||||
|
) gpu.Surface {
|
||||||
|
const glfw_native = glfw.Native(glfw_options);
|
||||||
|
const descriptor = if (glfw_options.win32) gpu.Surface.Descriptor{
|
||||||
|
.windows_hwnd = .{
|
||||||
|
.label = "basic surface",
|
||||||
|
.hinstance = std.os.windows.kernel32.GetModuleHandleW(null).?,
|
||||||
|
.hwnd = glfw_native.getWin32Window(window),
|
||||||
|
},
|
||||||
|
} else if (glfw_options.x11) gpu.Surface.Descriptor{
|
||||||
|
.xlib = .{
|
||||||
|
.label = "basic surface",
|
||||||
|
.display = glfw_native.getX11Display(),
|
||||||
|
.window = glfw_native.getX11Window(window),
|
||||||
|
},
|
||||||
|
} else if (glfw_options.cocoa) blk: {
|
||||||
|
const ns_window = glfw_native.getCocoaWindow(window);
|
||||||
|
const ns_view = msgSend(ns_window, "contentView", .{}, *anyopaque); // [nsWindow contentView]
|
||||||
|
|
||||||
|
// Create a CAMetalLayer that covers the whole window that will be passed to CreateSurface.
|
||||||
|
msgSend(ns_view, "setWantsLayer:", .{true}, void); // [view setWantsLayer:YES]
|
||||||
|
const layer = msgSend(objc.objc_getClass("CAMetalLayer"), "layer", .{}, ?*anyopaque); // [CAMetalLayer layer]
|
||||||
|
if (layer == null) @panic("failed to create Metal layer");
|
||||||
|
msgSend(ns_view, "setLayer:", .{layer.?}, void); // [view setLayer:layer]
|
||||||
|
|
||||||
|
// Use retina if the window was created with retina support.
|
||||||
|
const scale_factor = msgSend(ns_window, "backingScaleFactor", .{}, f64); // [ns_window backingScaleFactor]
|
||||||
|
msgSend(layer.?, "setContentsScale:", .{scale_factor}, void); // [layer setContentsScale:scale_factor]
|
||||||
|
|
||||||
|
break :blk gpu.Surface.Descriptor{
|
||||||
|
.metal_layer = .{
|
||||||
|
.label = "basic surface",
|
||||||
|
.layer = layer.?,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (glfw_options.wayland) {
|
||||||
|
@panic("Dawn does not yet have Wayland support, see https://bugs.chromium.org/p/dawn/issues/detail?id=1246&q=surface&can=2");
|
||||||
|
} else unreachable;
|
||||||
|
|
||||||
|
return native_instance.createSurface(&descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Borrowed from https://github.com/hazeycode/zig-objcrt
|
||||||
|
fn msgSend(obj: anytype, sel_name: [:0]const u8, args: anytype, comptime ReturnType: type) ReturnType {
|
||||||
|
const args_meta = @typeInfo(@TypeOf(args)).Struct.fields;
|
||||||
|
|
||||||
|
const FnType = switch (args_meta.len) {
|
||||||
|
0 => fn (@TypeOf(obj), objc.SEL) callconv(.C) ReturnType,
|
||||||
|
1 => fn (@TypeOf(obj), objc.SEL, args_meta[0].field_type) callconv(.C) ReturnType,
|
||||||
|
2 => fn (@TypeOf(obj), objc.SEL, args_meta[0].field_type, args_meta[1].field_type) callconv(.C) ReturnType,
|
||||||
|
3 => fn (@TypeOf(obj), objc.SEL, args_meta[0].field_type, args_meta[1].field_type, args_meta[2].field_type) callconv(.C) ReturnType,
|
||||||
|
4 => fn (@TypeOf(obj), objc.SEL, args_meta[0].field_type, args_meta[1].field_type, args_meta[2].field_type, args_meta[3].field_type) callconv(.C) ReturnType,
|
||||||
|
else => @compileError("Unsupported number of args"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: func is a var because making it const causes a compile error which I believe is a compiler bug
|
||||||
|
var func = @ptrCast(FnType, objc.objc_msgSend);
|
||||||
|
const sel = objc.sel_getUid(sel_name);
|
||||||
|
|
||||||
|
return @call(.{}, func, .{ obj, sel } ++ args);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue