Initial commit.

This commit is contained in:
Signal 2026-02-22 17:45:44 -05:00
commit 82b4f23c06
56 changed files with 3485 additions and 0 deletions

6
Client/CMakeLists.txt Normal file
View file

@ -0,0 +1,6 @@
file(GLOB_RECURSE MY_SOURCES CONFIGURE_DEPENDS
"*.cpp"
"*.h"
)
add_library(Client ${MY_SOURCES})

56
Client/Client.cpp Normal file
View file

@ -0,0 +1,56 @@
#include <chrono>
#include "Client.h"
#include "Network.h"
#include "Graphics/UIRenderer.h"
#include "Graphics/Graphics.h"
#include <Network/Network.h>
namespace Artifact {
void Client::init() {
window = subsystem<WindowImpl>();
for (auto & system : subsystems) {
system->client = this;
system->init();
system->reload();
}
}
void Client::render() {
for (auto & system : subsystems) {
system->render();
}
}
void Client::run() {
init();
auto time = std::chrono::steady_clock::now();
while (!window->shouldClose()) {
auto now = std::chrono::steady_clock::now();
if (time - now > std::chrono::milliseconds(50)) {
tick();
}
render();
}
}
void Client::tick() {
for (auto & system : subsystems) {
system->tick();
}
}
void Client::addDefaultSubsystems() {
addSubsystem<Window>(1080, 640, "Artifact Engine");
addSubsystem<ClientNetwork>();
auto graphics = addSubsystem<Graphics>();
{
graphics->addSubsystem<UIRenderer>();
}
}
}

40
Client/Client.h Normal file
View file

@ -0,0 +1,40 @@
#pragma once
#include <memory>
#include "Shared.h"
#include <Settings.h>
namespace Artifact {
class ClientSubsystem;
class WindowImpl;
/// The client class.
class Client: public Engine<ClientSubsystem> {
public:
WindowImpl * window = nullptr;
Settings settings;
Client() : settings(getClientConfigPath()) {}
void addDefaultSubsystems();
void initDefault();
/// Initializes the client. This includes opening a window, creating a WebGPU device, etc.
void init();
/// Runs a single iteration of the render loop.
void render();
/// Runs a single iteration of the tick loop.
void tick();
/// Runs the client.
void run();
};
class ClientSubsystem: public BaseSubsystem {
public:
Client * client = nullptr;
virtual void render() {}
virtual void tick() {}
};
}

View file

@ -0,0 +1,7 @@
#include "Camera.h"
namespace Artifact {
}

24
Client/Graphics/Camera.h Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include <vector>
#include <typeindex>
#include <glm/glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
namespace Artifact {
class Camera {
public:
glm::vec3 pos;
glm::quat rot;
double fov;
float near;
float far;
std::vector<std::type_index> renderPasses;
Camera(glm::vec3 pos, double fov, float near, float far) : pos(pos), fov(fov), near(near), far(far) {}
};
}

View file

@ -0,0 +1 @@
#include "ChunkRenderer.h"

View file

@ -0,0 +1,22 @@
#pragma once
#include <unordered_map>
#include <glm/glm/glm.hpp>
#include <World/Chunk.h>
#include <Shared.h>
#include "Graphics.h"
namespace Artifact {
class ChunkRenderer: public GraphicsSubsystem {
std::unordered_map<glm::ivec3, Chunk> chunks;
public:
void render() {
}
};
}

View file

@ -0,0 +1,7 @@
#include "EntityRenderer.h"
namespace Artifact {
}

View file

@ -0,0 +1,12 @@
#pragma once
#include <Shared.h>
#include "Graphics.h"
namespace Artifact {
class EntityRenderer: public GraphicsSubsystem {
};
}

View file

@ -0,0 +1,168 @@
#include "Graphics.h"
#include "../Platform/Window.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
namespace Artifact {
void Graphics::init() {
instance = wgpuCreateInstance(nullptr);
window = client->subsystem<WindowImpl>();
surface = window->createWGPUSurface(instance);
WGPURequestAdapterOptions opts {
.compatibleSurface = surface
};
wgpuInstanceRequestAdapter(instance, &opts, {
.mode = WGPUCallbackMode_AllowProcessEvents,
.callback = [](auto status, auto adapter, auto msg, auto userdata, auto) {
if (status == WGPURequestAdapterStatus_Success) {
static_cast<Graphics *>(userdata)->adapter = adapter;
} else {
throw std::runtime_error("Failed to get wgpu adapter.");
}
},
.userdata1 = this
});
// To avoid dealing with concurrency, we just block until the adapter is available (or the program errors).
while (!adapter) wgpuInstanceProcessEvents(instance);
WGPUDeviceDescriptor desc {
.uncapturedErrorCallbackInfo = {
.callback = [](auto device, auto type, auto msg, auto, auto) {
const char * message = (msg.data ? reinterpret_cast<const char *>(msg.data) : "No message");
fprintf(stderr, "Uncaptured WebGPU Error (type %d): %s\n", type, message);
}}
};
wgpuAdapterRequestDevice(adapter, &desc, {
.mode = WGPUCallbackMode_AllowProcessEvents,
.callback = [](auto status, auto device, auto msg, auto userdata, auto) {
if (status == WGPURequestDeviceStatus_Success) {
static_cast<Graphics *>(userdata)->device = device;
} else {
throw std::runtime_error("Failed to get wgpu device.");
}
},
.userdata1 = this
});
while (!device) wgpuInstanceProcessEvents(instance);
WGPUTextureDescriptor depthDesc = {};
depthDesc.dimension = WGPUTextureDimension_2D;
depthDesc.size.width = 1080;
depthDesc.size.height = 640;
depthDesc.size.depthOrArrayLayers = 1;
depthDesc.format = WGPUTextureFormat_Depth32Float;
depthDesc.usage = WGPUTextureUsage_RenderAttachment;
depthDesc.mipLevelCount = 1;
depthDesc.sampleCount = 1;
depthTexture = wgpuDeviceCreateTexture(device, &depthDesc);
depthTextureView = wgpuTextureCreateView(depthTexture, nullptr);
queue = wgpuDeviceGetQueue(device);
WGPUSurfaceConfiguration surfaceConfig = {};
surfaceConfig.device = device;
surfaceConfig.format = WGPUTextureFormat_BGRA8Unorm; // FIXME: Auto-detect
surfaceConfig.usage = WGPUTextureUsage_RenderAttachment;
surfaceConfig.width = 1080;
surfaceConfig.height = 640;
surfaceConfig.presentMode = WGPUPresentMode_Fifo;
wgpuSurfaceConfigure(surface, &surfaceConfig);
for (auto & system : subsystems) {
system->graphics = this;
printf("Graphics: %p", system->graphics);
system->init();
system->reload();
}
}
void Graphics::deinit() {
for (auto & system : subsystems) {
system->deinit();
}
wgpuTextureDestroy(depthTexture);
wgpuQueueRelease(queue);
wgpuDeviceDestroy(device);
wgpuAdapterRelease(adapter);
wgpuSurfaceRelease(surface);
wgpuInstanceRelease(instance);
}
void Graphics::render() {
WGPUSurfaceTexture surfaceTexture;
wgpuSurfaceGetCurrentTexture(surface, &surfaceTexture);
if (surfaceTexture.status == WGPUSurfaceGetCurrentTextureStatus_Error || !surfaceTexture.texture) {
throw std::runtime_error("Failed to get surface texture");
}
WGPUTextureView nextTexture = wgpuTextureCreateView(surfaceTexture.texture, nullptr);
if (!nextTexture) {
throw std::runtime_error("Failed to create texture view");
}
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, nullptr);
for (auto & system : subsystems) {
system->render(nextTexture, encoder);
}
WGPUCommandBufferDescriptor cmdBufferDesc = {};
cmdBufferDesc.label = WGPUStringView{"Command buffer", WGPU_STRLEN};
WGPUCommandBuffer cmdBuffer = wgpuCommandEncoderFinish(encoder, nullptr);
wgpuQueueSubmit(queue, 1, &cmdBuffer);
#ifndef WASM_BUILD
wgpuSurfacePresent(surface);
#endif
wgpuTextureViewRelease(nextTexture);
wgpuTextureRelease(surfaceTexture.texture);
}
WGPUTexture Graphics::createTextureFromData(const void* data, uint32_t width, uint32_t height) {
WGPUTextureDescriptor textureDesc = {};
textureDesc.dimension = WGPUTextureDimension_2D;
textureDesc.size.width = width;
textureDesc.size.height = height;
textureDesc.size.depthOrArrayLayers = 1;
textureDesc.format = WGPUTextureFormat_RGBA8Unorm;
textureDesc.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst;
textureDesc.mipLevelCount = 1;
textureDesc.sampleCount = 1;
auto texture = wgpuDeviceCreateTexture(device, &textureDesc);
// WGPUTextureViewDescriptor viewDesc = {};
// viewDesc.format = WGPUTextureFormat_RGBA8Unorm;
// viewDesc.dimension = WGPUTextureViewDimension_2D;
// viewDesc.mipLevelCount = 1;
// viewDesc.arrayLayerCount = 1;
// tex->view = wgpuTextureCreateView(tex->texture, &viewDesc);
WGPUTexelCopyTextureInfo destination = {};
destination.texture = texture;
destination.mipLevel = 0;
destination.origin = {0, 0, 0};
destination.aspect = WGPUTextureAspect_All;
WGPUTexelCopyBufferLayout layout = {};
layout.offset = 0;
layout.bytesPerRow = width * 4;
layout.rowsPerImage = height;
WGPUExtent3D writeSize = {width, height, 1};
wgpuQueueWriteTexture(queue, &destination, data, width * height * 4, &layout, &writeSize);
return texture;
}
WGPUTexture Graphics::createTextureFromFile(const char* file) {
int width, height, channels;
auto data = stbi_load(file, &width, &height, &channels, 4);
auto out = createTextureFromData(data, width, height);
stbi_image_free(data);
return out;
}
}

View file

@ -0,0 +1,43 @@
#pragma once
#include <webgpu/webgpu.h>
#include <Shared.h>
#include "../Client.h"
#include "../Platform/Window.h"
namespace Artifact {
class Graphics;
class GraphicsSubsystem {
public:
Graphics * graphics;
virtual void init() {}
virtual void reload() {}
virtual void deinit() {}
virtual void render(WGPUTextureView nextTexture, WGPUCommandEncoder encoder) {}
};
class Graphics: public ClientSubsystem, public Engine<GraphicsSubsystem> {
public:
WGPUInstance instance = nullptr;
WGPUSurface surface = nullptr;
WGPUAdapter adapter = nullptr;
WGPUDevice device = nullptr;
WGPUTexture depthTexture = nullptr;
WGPUTextureView depthTextureView = nullptr;
WGPUQueue queue = nullptr;
WindowImpl * window;
void init() override;
void deinit() override;
void render() override;
WGPUTexture createTextureFromData(const void* data, uint32_t width, uint32_t height);
WGPUTexture createTextureFromFile(const char* file);
};
}

View file

@ -0,0 +1,429 @@
#include "UIRenderer.h"
#include <iostream>
#define NK_IMPLEMENTATION
#include <nuklear.h>
#include <glm/gtc/matrix_transform.hpp>
namespace Artifact {
namespace Events {
struct DrawUI {};
}
static std::array<nk_keys, static_cast<size_t>(Key::Last) + 1> ArtifactToNuklear {};
namespace {
static struct _nkInit {
_nkInit() {
// Populate the Artifact-to-Nuklear key map.
#define X(nuklear, artifact) ArtifactToNuklear[static_cast<int>(Key::artifact)] = nuklear
X(NK_KEY_CTRL, ControlLeft);
X(NK_KEY_CTRL, ControlRight);
X(NK_KEY_SHIFT, ShiftLeft);
X(NK_KEY_SHIFT, ShiftRight);
X(NK_KEY_ENTER, Enter);
X(NK_KEY_BACKSPACE, DeleteBackward);
X(NK_KEY_DEL, DeleteForward);
X(NK_KEY_LEFT, ArrowLeft);
X(NK_KEY_RIGHT, ArrowRight);
X(NK_KEY_UP, ArrowUp);
X(NK_KEY_DOWN, ArrowDown);
X(NK_KEY_TAB, Tab);
#undef X
}
} __nkInit;
}
void UIRenderer::init() {
printf("UI: %p", graphics);
graphics->window->listen<Events::InputBegin>([this](auto ev) {
nk_input_begin(&ctx);
});
graphics->window->listen<Events::InputEnd>([this](auto ev) {
nk_input_end(&ctx);
});
graphics->window->listen<Events::CursorPosEvent>([this](auto ev) {
nk_input_motion(&ctx, ev.x, ev.y);
});
graphics->window->listen<Events::ScrollEvent>([this](auto ev) {
nk_input_scroll(&ctx, nk_vec2(ev.dx, ev.dy));
});
graphics->window->listen<Events::MouseEvent>([this](auto ev) {
nk_input_button(&ctx, ev.button == Events::MOUSE_BUTTON_RIGHT ? NK_BUTTON_RIGHT : NK_BUTTON_LEFT, (int)ev.x, (int)ev.y, ev.state);
});
graphics->window->listen<Events::KeyDownEvent>([this](auto ev) {
nk_input_key(&ctx, ArtifactToNuklear[static_cast<int>(ev.key)], true);
});
graphics->window->listen<Events::KeyUpEvent>([this](auto ev) {
nk_input_key(&ctx, ArtifactToNuklear[static_cast<int>(ev.key)], false);
});
graphics->window->listen<Events::CharInputEvent>([this](auto ev) {
nk_input_char(&ctx, ev.codepoint);
});
nk_init_default(&ctx, nullptr);
nk_font_atlas_init_default(&atlas);
nk_font_atlas_begin(&atlas);
nk_font* font;
void* fontData = nullptr;
// FIXME: Automatic path resolution
std::string assetPath = std::string(getenv("HOME")) + "/eclipse-workspace/ArtifactEngine/assets";
FILE* fontFile = fopen((assetPath + "/fonts/Arial.ttf").c_str(), "rb");
if (fontFile) {
fseek(fontFile, 0, SEEK_END);
long fontSize = ftell(fontFile);
fseek(fontFile, 0, SEEK_SET);
fontData = malloc(fontSize);
fread(fontData, 1, fontSize, fontFile);
fclose(fontFile);
font = nk_font_atlas_add_from_memory(&atlas, fontData, fontSize, 13.0f, nullptr);
} else {
font = nk_font_atlas_add_default(&atlas, 13.0f, nullptr);
}
int fontWidth, fontHeight;
const void* fontImage = nk_font_atlas_bake(&atlas, &fontWidth, &fontHeight, NK_FONT_ATLAS_RGBA32);
fontTexture = graphics->createTextureFromData(fontImage, fontWidth, fontHeight);
fontTextureView = wgpuTextureCreateView(fontTexture, nullptr);
nk_font_atlas_end(&atlas, nk_handle_id(0), nullptr);
nk_style_set_font(&ctx, &font->handle);
if (fontData) free(fontData);
unsigned char whitePixel[4] = {255, 255, 255, 255};
dummyTexture = graphics->createTextureFromData(whitePixel, 1, 1);
dummyTextureView = wgpuTextureCreateView(dummyTexture, nullptr);
nk_buffer_init_default(&vertexBufferNK);
nk_buffer_init_default(&indexBufferNK);
nk_buffer_init_default(&commandBufferNK);
}
struct NKVertex {
float pos[2];
float uv[2];
uint8_t color[4];
};
void UIRenderer::reload() {
WGPUBufferDescriptor uiVertexBufferDesc = {};
uiVertexBufferDesc.size = 1024 * 1024 * sizeof(NKVertex);
uiVertexBufferDesc.usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst;
uiVertexBufferDesc.label = WGPUStringView{"UI Vertex Buffer", WGPU_STRLEN};
vertexBuffer = wgpuDeviceCreateBuffer(graphics->device, &uiVertexBufferDesc);
WGPUBufferDescriptor uiIndexBufferDesc = {};
uiIndexBufferDesc.size = 1024 * 1024 * sizeof(uint16_t);
uiIndexBufferDesc.usage = WGPUBufferUsage_Index | WGPUBufferUsage_CopyDst;
uiIndexBufferDesc.label = WGPUStringView{"UI Index Buffer", WGPU_STRLEN};
indexBuffer = wgpuDeviceCreateBuffer(graphics->device, &uiIndexBufferDesc);
WGPUBufferDescriptor uiUniformBufferDesc = {};
uiUniformBufferDesc.size = sizeof(glm::mat4);
uiUniformBufferDesc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst;
uiUniformBufferDesc.label = WGPUStringView{"UI Uniform Buffer", WGPU_STRLEN};
uniformBuffer = wgpuDeviceCreateBuffer(graphics->device, &uiUniformBufferDesc);
std::string uiVSCode = R"(
struct Uniforms {
ortho: mat4x4<f32>,
}
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
struct VertexOutput {
@builtin(position) pos: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) color: vec4<f32>,
}
@vertex
fn vs_main(@location(0) pos: vec2<f32>, @location(1) uv: vec2<f32>, @location(2) color: vec4<f32>) -> VertexOutput {
var out: VertexOutput;
out.pos = uniforms.ortho * vec4<f32>(pos, 0.0, 1.0);
out.uv = uv;
out.color = color;
return out;
}
)";
std::string uiFSCode = R"(
@group(0) @binding(1) var texture: texture_2d<f32>;
@group(0) @binding(2) var sampler_: sampler;
@fragment
fn fs_main(@location(0) uv: vec2<f32>, @location(1) color: vec4<f32>) -> @location(0) vec4<f32> {
return textureSample(texture, sampler_, uv) * color;
}
)";
WGPUShaderModuleDescriptor uiVSDesc = {};
WGPUShaderSourceWGSL src = {.code = WGPUStringView{uiVSCode.c_str(), uiVSCode.size()}};
src.chain.sType = WGPUSType_ShaderSourceWGSL;
uiVSDesc.nextInChain = &src.chain;
WGPUShaderModule uiVSModule = wgpuDeviceCreateShaderModule(graphics->device, &uiVSDesc);
WGPUShaderModuleDescriptor uiFSDesc = {};
src = {.code = WGPUStringView{uiFSCode.c_str(), uiFSCode.size()}};
src.chain.sType = WGPUSType_ShaderSourceWGSL;
uiFSDesc.nextInChain = &src.chain;
WGPUShaderModule uiFSModule = wgpuDeviceCreateShaderModule(graphics->device, &uiFSDesc);
WGPUSamplerDescriptor uiSamplerDesc = {};
uiSamplerDesc.addressModeU = WGPUAddressMode_ClampToEdge;
uiSamplerDesc.addressModeV = WGPUAddressMode_ClampToEdge;
uiSamplerDesc.addressModeW = WGPUAddressMode_ClampToEdge;
uiSamplerDesc.magFilter = WGPUFilterMode_Linear;
uiSamplerDesc.minFilter = WGPUFilterMode_Linear;
uiSamplerDesc.mipmapFilter = WGPUMipmapFilterMode_Linear;
uiSamplerDesc.maxAnisotropy = 1;
sampler = wgpuDeviceCreateSampler(graphics->device, &uiSamplerDesc);
if (!sampler) {
throw std::runtime_error("Failed to create UI sampler");
}
WGPUBindGroupLayoutEntry uiBglEntries[3] = {};
uiBglEntries[0].binding = 0;
uiBglEntries[0].visibility = WGPUShaderStage_Vertex;
uiBglEntries[0].buffer.type = WGPUBufferBindingType_Uniform;
uiBglEntries[1].binding = 1;
uiBglEntries[1].visibility = WGPUShaderStage_Fragment;
uiBglEntries[1].texture.sampleType = WGPUTextureSampleType_Float;
uiBglEntries[1].texture.viewDimension = WGPUTextureViewDimension_2D;
uiBglEntries[2].binding = 2;
uiBglEntries[2].visibility = WGPUShaderStage_Fragment;
uiBglEntries[2].sampler.type = WGPUSamplerBindingType_Filtering;
WGPUBindGroupLayoutDescriptor uiBglDesc = {};
uiBglDesc.entryCount = 3;
uiBglDesc.entries = uiBglEntries;
bgl = wgpuDeviceCreateBindGroupLayout(graphics->device, &uiBglDesc);
if (!bgl) {
throw std::runtime_error("Failed to create bind group layout");
}
WGPUVertexAttribute uiAttributes[3] = {};
uiAttributes[0].format = WGPUVertexFormat_Float32x2;
uiAttributes[0].offset = offsetof(NKVertex, pos);
uiAttributes[0].shaderLocation = 0;
uiAttributes[1].format = WGPUVertexFormat_Float32x2;
uiAttributes[1].offset = offsetof(NKVertex, uv);
uiAttributes[1].shaderLocation = 1;
uiAttributes[2].format = WGPUVertexFormat_Unorm8x4;
uiAttributes[2].offset = offsetof(NKVertex, color);
uiAttributes[2].shaderLocation = 2;
WGPUVertexBufferLayout uiVBLayout = {};
uiVBLayout.arrayStride = sizeof(NKVertex);
uiVBLayout.stepMode = WGPUVertexStepMode_Vertex;
uiVBLayout.attributeCount = 3;
uiVBLayout.attributes = uiAttributes;
WGPUBlendState uiBlendState = {};
uiBlendState.color.srcFactor = WGPUBlendFactor_SrcAlpha;
uiBlendState.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha;
uiBlendState.color.operation = WGPUBlendOperation_Add;
uiBlendState.alpha.srcFactor = WGPUBlendFactor_One;
uiBlendState.alpha.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha;
uiBlendState.alpha.operation = WGPUBlendOperation_Add;
WGPUColorTargetState uiColorTarget = {};
uiColorTarget.format = WGPUTextureFormat_BGRA8Unorm;
uiColorTarget.blend = &uiBlendState;
uiColorTarget.writeMask = WGPUColorWriteMask_All;
WGPUFragmentState uiFragmentState = {};
uiFragmentState.module = uiFSModule;
uiFragmentState.entryPoint = WGPUStringView{"fs_main", WGPU_STRLEN};
uiFragmentState.targetCount = 1;
uiFragmentState.targets = &uiColorTarget;
WGPUPipelineLayoutDescriptor uiPlDesc = {};
uiPlDesc.bindGroupLayoutCount = 1;
uiPlDesc.bindGroupLayouts = &bgl;
WGPUPipelineLayout uiPipelineLayout = wgpuDeviceCreatePipelineLayout(graphics->device, &uiPlDesc);
WGPURenderPipelineDescriptor uiPipelineDesc = {};
uiPipelineDesc.vertex.module = uiVSModule;
uiPipelineDesc.vertex.entryPoint = WGPUStringView{"vs_main", WGPU_STRLEN};
uiPipelineDesc.vertex.bufferCount = 1;
uiPipelineDesc.vertex.buffers = &uiVBLayout;
uiPipelineDesc.fragment = &uiFragmentState;
uiPipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
uiPipelineDesc.primitive.frontFace = WGPUFrontFace_CCW;
uiPipelineDesc.primitive.cullMode = WGPUCullMode_None;
uiPipelineDesc.layout = uiPipelineLayout;
uiPipelineDesc.multisample.count = 1;
uiPipelineDesc.multisample.mask = ~0u;
pipeline = wgpuDeviceCreateRenderPipeline(graphics->device, &uiPipelineDesc);
}
std::string txt = "Test";
int _txt = 0;
void UIRenderer::render(WGPUTextureView nextTexture, WGPUCommandEncoder encoder) {
if (nk_begin(&ctx, "Test", nk_rect(10, 10, 100, 100), NK_WINDOW_TITLE | NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE)) {
nk_layout_row_dynamic(&ctx, 50, 2);
nk_label(&ctx, "Test", 0);
nk_button_label(&ctx, "Test");
nk_layout_row_dynamic(&ctx, 50, 2);
nk_label(&ctx, "Test", 0);
nk_button_label(&ctx, "Test");
nk_layout_row_dynamic(&ctx, 50, 1);
nk_edit_string(&ctx, NK_EDIT_BOX, txt.data(), &_txt, 100, nk_filter_ascii);
if (nk_contextual_begin(&ctx, NK_PANEL_CONTEXTUAL, nk_vec2(100, 100), nk_rect(10, 10, 100, 100))) {
nk_layout_row_dynamic(&ctx, 25, 1);
nk_contextual_item_label(&ctx, "Button", NK_TEXT_CENTERED);
nk_contextual_end(&ctx);
}
}
nk_end(&ctx);
dispatch(Events::DrawUI {});
nk_buffer_clear(&vertexBufferNK);
nk_buffer_clear(&indexBufferNK);
nk_buffer_clear(&commandBufferNK);
struct nk_convert_config config = {};
static const struct nk_draw_vertex_layout_element vertex_layout[] = {
{NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(NKVertex, pos)},
{NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(NKVertex, uv)},
{NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(NKVertex, color)},
{NK_VERTEX_LAYOUT_END}
};
config.vertex_layout = vertex_layout;
config.vertex_size = sizeof(NKVertex);
config.vertex_alignment = alignof(NKVertex);
config.tex_null.texture = nk_handle_id(0);
config.tex_null.uv = {0.0f, 0.0f};
config.circle_segment_count = 22;
config.curve_segment_count = 22;
config.arc_segment_count = 22;
config.global_alpha = 1.0f;
config.shape_AA = NK_ANTI_ALIASING_ON;
config.line_AA = NK_ANTI_ALIASING_ON;
nk_convert(&ctx, &commandBufferNK, &vertexBufferNK, &indexBufferNK, &config);
// FIXME: Variable size
int viewportWidth = 1080;
int viewportHeight = 640;
auto ortho = glm::ortho(0.0f, (float)viewportWidth, (float)viewportHeight, 0.0f, -1.0f, 1.0f);
wgpuQueueWriteBuffer(graphics->queue, uniformBuffer, 0, &ortho, sizeof(ortho));
WGPURenderPassColorAttachment colorAttachment = {};
colorAttachment.view = nextTexture;
colorAttachment.loadOp = WGPULoadOp_Load;
colorAttachment.storeOp = WGPUStoreOp_Store;
colorAttachment.clearValue = {0, 0.2, 0.4, 1};
colorAttachment.depthSlice = -1;
WGPURenderPassDepthStencilAttachment depthAttachment = {};
depthAttachment.view = graphics->depthTextureView;
depthAttachment.depthLoadOp = WGPULoadOp_Clear;
depthAttachment.depthStoreOp = WGPUStoreOp_Store;
depthAttachment.depthClearValue = 1.0f;
depthAttachment.depthReadOnly = false;
depthAttachment.stencilLoadOp = WGPULoadOp_Undefined;
depthAttachment.stencilStoreOp = WGPUStoreOp_Undefined;
WGPURenderPassDescriptor renderPassDesc = {};
renderPassDesc.colorAttachmentCount = 1;
renderPassDesc.colorAttachments = &colorAttachment;
renderPassDesc.depthStencilAttachment = &depthAttachment;
colorAttachment.loadOp = WGPULoadOp_Load;
WGPURenderPassDescriptor uiRenderPassDesc = {};
uiRenderPassDesc.colorAttachmentCount = 1;
uiRenderPassDesc.colorAttachments = &colorAttachment;
WGPURenderPassEncoder uiRenderPass = wgpuCommandEncoderBeginRenderPass(encoder, &uiRenderPassDesc);
wgpuRenderPassEncoderSetPipeline(uiRenderPass, pipeline);
wgpuRenderPassEncoderSetViewport(uiRenderPass, 0, 0, (float)viewportWidth, (float)viewportHeight, 0.0f, 1.0f);
size_t vertexSize = nk_buffer_total(&vertexBufferNK);
size_t indexSize = nk_buffer_total(&indexBufferNK);
if (vertexSize > 0 && indexSize > 0) {
wgpuQueueWriteBuffer(graphics->queue, vertexBuffer, 0, vertexBufferNK.memory.ptr, vertexSize);
wgpuQueueWriteBuffer(graphics->queue, indexBuffer, 0, indexBufferNK.memory.ptr, indexSize);
wgpuRenderPassEncoderSetVertexBuffer(uiRenderPass, 0, vertexBuffer, 0, vertexSize);
wgpuRenderPassEncoderSetIndexBuffer(uiRenderPass, indexBuffer, WGPUIndexFormat_Uint16, 0, indexSize);
std::vector<WGPUBindGroup> bindGroups;
WGPUBindGroupEntry uiBgEntries[3] = {};
uiBgEntries[0].binding = 0;
uiBgEntries[0].buffer = uniformBuffer;
uiBgEntries[0].offset = 0;
uiBgEntries[0].size = sizeof(glm::mat4);
uiBgEntries[2].binding = 2;
uiBgEntries[2].sampler = sampler;
std::unordered_map<int, WGPUTextureView> textureMap = {{0, fontTextureView}};
const struct nk_draw_command* cmd;
uint32_t offset = 0;
nk_draw_foreach(cmd, &ctx, &commandBufferNK) {
if (!cmd->elem_count) continue;
if (offset + cmd->elem_count > indexSize / sizeof(uint16_t)) break;
uint32_t scissorX = static_cast<uint32_t>(cmd->clip_rect.x < 0 ? 0 : cmd->clip_rect.x);
uint32_t scissorY = static_cast<uint32_t>(cmd->clip_rect.y < 0 ? 0 : cmd->clip_rect.y);
uint32_t scissorW = static_cast<uint32_t>(cmd->clip_rect.w);
uint32_t scissorH = static_cast<uint32_t>(cmd->clip_rect.h);
scissorX = std::min(scissorX, (uint32_t)viewportWidth - 1);
scissorY = std::min(scissorY, (uint32_t)viewportHeight - 1);
if (scissorW > viewportWidth || scissorH > viewportHeight || scissorX + scissorW > viewportWidth || scissorY + scissorH > viewportHeight) {
scissorW = std::min(scissorW, viewportWidth - scissorX);
scissorH = std::min(scissorH, viewportHeight - scissorY);
}
wgpuRenderPassEncoderSetScissorRect(uiRenderPass, scissorX, scissorY, scissorW, scissorH);
uiBgEntries[1].binding = 1;
uiBgEntries[1].textureView = cmd->texture.id >= 0 && textureMap.count(cmd->texture.id) ? textureMap[cmd->texture.id] : dummyTextureView;
WGPUBindGroupDescriptor uiBgDesc = {};
uiBgDesc.layout = bgl;
uiBgDesc.entryCount = 3;
uiBgDesc.entries = uiBgEntries;
WGPUBindGroup uiDynamicBindGroup = wgpuDeviceCreateBindGroup(graphics->device, &uiBgDesc);
bindGroups.push_back(uiDynamicBindGroup);
wgpuRenderPassEncoderSetBindGroup(uiRenderPass, 0, uiDynamicBindGroup, 0, nullptr);
wgpuRenderPassEncoderDrawIndexed(uiRenderPass, cmd->elem_count, 1, offset, 0, 0);
offset += cmd->elem_count;
}
wgpuRenderPassEncoderEnd(uiRenderPass);
for (WGPUBindGroup bg : bindGroups) wgpuBindGroupRelease(bg);
} else {
wgpuRenderPassEncoderEnd(uiRenderPass);
}
nk_clear(&ctx);
}
}

View file

@ -0,0 +1,46 @@
#pragma once
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT
#include <nuklear.h>
#include <webgpu/webgpu.h>
#include <Shared.h>
#include "Graphics.h"
#include "../Platform/Window.h"
namespace Artifact {
class UIRenderer: public GraphicsSubsystem, public EventTarget {
nk_context ctx {};
nk_font_atlas atlas {};
WGPURenderPipeline pipeline = nullptr;
WGPUBuffer vertexBuffer = nullptr;
WGPUBuffer indexBuffer = nullptr;
WGPUBuffer uniformBuffer = nullptr;
WGPUSampler sampler = nullptr;
WGPUBindGroupLayout bgl = nullptr;
WGPUTexture fontTexture = nullptr;
WGPUTextureView fontTextureView = nullptr;
WGPUTexture dummyTexture = nullptr;
WGPUTextureView dummyTextureView = nullptr;
nk_buffer vertexBufferNK;
nk_buffer indexBufferNK;
nk_buffer commandBufferNK;
void init() override;
void reload() override;
void render(WGPUTextureView nextTexture, WGPUCommandEncoder encoder) override;
WGPURenderPipeline makePipeline();
};
}

View file

@ -0,0 +1 @@
#include "WorldRenderer.h"

View file

@ -0,0 +1,11 @@
#pragma once
#include <Shared.h>
namespace Artifact {
class WorldRenderer {
};
}

17
Client/LocalPlayer.cpp Normal file
View file

@ -0,0 +1,17 @@
#include "LocalPlayer.h"
#include "Platform/Window.h"
namespace Artifact {
void LocalPlayer::init() {
window = client->subsystem<WindowImpl>();
}
void LocalPlayer::tick() {
if (window->isKeyDown(Key::W)) {
}
}
}

26
Client/LocalPlayer.h Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include <Shared.h>
#include "Client.h"
#include "Graphics/Camera.h"
namespace Artifact {
class LocalPlayerImpl: public ClientSubsystem {
public:
std::unique_ptr<Camera> camera = nullptr;
};
class LocalPlayer: public LocalPlayerImpl {
WindowImpl * window = nullptr;
public:
LocalPlayer() {
//camera = std::make_unique<Camera>(glm::vec3(0, 0, 0), 75);
}
void init() override;
void tick() override;
};
}

11
Client/Network.h Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include "Client.h"
#include <Network/Network.h>
namespace Artifact {
class ClientNetwork: public NetworkClient, public ClientSubsystem {};
}

View file

@ -0,0 +1,12 @@
#include "Keybinds.h"
namespace Artifact {
namespace Keybinds {
}
}

View file

@ -0,0 +1,13 @@
#pragma once
#include "Window.h"
namespace Artifact {
namespace Keybinds {
}
}

177
Client/Platform/Window.cpp Normal file
View file

@ -0,0 +1,177 @@
#include <stdexcept>
#include "Window.h"
#include "glfw3webgpu/glfw3webgpu.h"
namespace Artifact {
static std::array<Key, GLFW_KEY_LAST + 1> GLFWToArtifact {};
static std::array<int, static_cast<size_t>(Key::Last) + 1> ArtifactToGLFW {};
namespace {
// Ensure that GLFW is transparently initialized prior to window creation.
static struct _glfwInit {
_glfwInit() {
if(!glfwInit()) throw std::runtime_error("Failed to initialize GLFW.");
// Populate the global GLFW-to-Artifact keycode mapping.
GLFWToArtifact.fill(Key::Unknown);
#define X(glfw, artifact) GLFWToArtifact[glfw] = Key::artifact;\
ArtifactToGLFW[static_cast<int>(Key::artifact)] = glfw
X(GLFW_KEY_A, A);
X(GLFW_KEY_B, B);
X(GLFW_KEY_C, C);
X(GLFW_KEY_D, D);
X(GLFW_KEY_E, E);
X(GLFW_KEY_F, F);
X(GLFW_KEY_G, G);
X(GLFW_KEY_H, H);
X(GLFW_KEY_I, I);
X(GLFW_KEY_J, J);
X(GLFW_KEY_K, K);
X(GLFW_KEY_L, L);
X(GLFW_KEY_M, M);
X(GLFW_KEY_N, N);
X(GLFW_KEY_O, O);
X(GLFW_KEY_P, P);
X(GLFW_KEY_Q, Q);
X(GLFW_KEY_R, R);
X(GLFW_KEY_S, S);
X(GLFW_KEY_T, T);
X(GLFW_KEY_U, U);
X(GLFW_KEY_V, V);
X(GLFW_KEY_W, W);
X(GLFW_KEY_X, X);
X(GLFW_KEY_Y, Y);
X(GLFW_KEY_Z, Z);
X(GLFW_KEY_0, Zero);
X(GLFW_KEY_1, One);
X(GLFW_KEY_2, Two);
X(GLFW_KEY_3, Three);
X(GLFW_KEY_4, Four);
X(GLFW_KEY_5, Five);
X(GLFW_KEY_6, Six);
X(GLFW_KEY_7, Seven);
X(GLFW_KEY_8, Eight);
X(GLFW_KEY_9, Nine);
X(GLFW_KEY_ENTER, Enter);
X(GLFW_KEY_LEFT_SHIFT, ShiftLeft);
X(GLFW_KEY_RIGHT_SHIFT, ShiftRight);
X(GLFW_KEY_LEFT_CONTROL, ControlLeft);
X(GLFW_KEY_RIGHT_CONTROL, ControlRight);
X(GLFW_KEY_LEFT_ALT, OptionLeft);
X(GLFW_KEY_RIGHT_ALT, OptionRight);
X(GLFW_KEY_LEFT_SUPER, CommandLeft);
X(GLFW_KEY_RIGHT_SUPER, CommandRight);
X(GLFW_KEY_BACKSPACE, DeleteBackward);
X(GLFW_KEY_DELETE, DeleteForward);
X(GLFW_KEY_LEFT, ArrowLeft);
X(GLFW_KEY_RIGHT, ArrowRight);
X(GLFW_KEY_UP, ArrowUp);
X(GLFW_KEY_DOWN, ArrowDown);
X(GLFW_KEY_TAB, Tab);
#undef X
}
~_glfwInit() {
glfwTerminate();
}
} __glfwInit;
}
Window::Window(uint32_t width, uint32_t height, std::string title) {
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
window = glfwCreateWindow(width, height, "", nullptr, nullptr);
glfwSetWindowUserPointer(window, this);
glfwSetCursorPosCallback(window, [](auto window, auto x, auto y) {
static_cast<Window *>(glfwGetWindowUserPointer(window))->dispatch(Events::CursorPosEvent {
.x = x,
.y = y
});
});
glfwSetMouseButtonCallback(window, [](auto window, auto button, auto action, auto mods) {
Events::MouseButton myButton = Events::MOUSE_BUTTON_LEFT;
if (button == GLFW_MOUSE_BUTTON_2) {
myButton = Events::MOUSE_BUTTON_RIGHT;
} else if (button == GLFW_MOUSE_BUTTON_3) {
myButton = Events::MOUSE_BUTTON_MIDDLE;
}
double x, y;
glfwGetCursorPos(window, &x, &y);
static_cast<Window *>(glfwGetWindowUserPointer(window))->dispatch(Events::MouseEvent {
.button = myButton,
.state = action == GLFW_PRESS,
.x = x,
.y = y
});
});
glfwSetScrollCallback(window, [](auto window, auto dx, auto dy) {
static_cast<Window *>(glfwGetWindowUserPointer(window))->dispatch(Events::ScrollEvent {
.dx = dx,
.dy = dy
});
});
glfwSetKeyCallback(window, [](auto window, auto key, auto scancode, auto action, auto mods) {
if (action == GLFW_PRESS) {
static_cast<Window *>(glfwGetWindowUserPointer(window))->dispatch(Events::KeyDownEvent {
.key = GLFWToArtifact[key]
});
} else if (action == GLFW_RELEASE) {
static_cast<Window *>(glfwGetWindowUserPointer(window))->dispatch(Events::KeyUpEvent {
.key = GLFWToArtifact[key]
});
}
});
glfwSetCharCallback(window, [](auto window, auto codepoint) {
static_cast<Window *>(glfwGetWindowUserPointer(window))->dispatch(Events::CharInputEvent {
.codepoint = codepoint
});
});
}
void Window::render() {
dispatch(Events::InputBegin {});
glfwPollEvents();
dispatch(Events::InputEnd {});
}
WGPUSurface Window::createWGPUSurface(WGPUInstance instance) {
return glfwCreateWindowWGPUSurface(instance, window);
}
void Window::setTitle(std::string title) {
glfwSetWindowTitle(window, title.c_str());
}
bool Window::shouldClose() {
return glfwWindowShouldClose(window);
}
void Window::setPointerLock() {
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
}
void Window::releasePointerLock() {
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
}
bool Window::isKeyDown(Key key) {
return glfwGetKey(window, ArtifactToGLFW[static_cast<int>(key)]) == GLFW_PRESS;
}
}

103
Client/Platform/Window.h Normal file
View file

@ -0,0 +1,103 @@
#pragma once
#include <string>
#include <GLFW/glfw3.h>
#include <webgpu/webgpu.h>
#include <Shared.h>
#include "Events.h"
#include "Client.h"
namespace Artifact {
enum class Key {
Unknown = 0,
A, B, C, D, E, F, G, H, I, J, K, L, M,
N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
Zero, One, Two, Three, Four, Five,
Six, Seven, Eight, Nine,
ShiftLeft, ShiftRight,
ControlLeft, ControlRight,
OptionLeft, OptionRight,
CommandLeft, CommandRight,
Enter,
DeleteForward, DeleteBackward,
ArrowLeft, ArrowRight, ArrowUp, ArrowDown,
Tab,
Last
};
namespace Events {
struct InputBegin {};
struct InputEnd {};
enum Action {
ACTION_PRESS,
ACTION_RELEASE
};
struct KeyDownEvent {
Key key;
};
struct KeyUpEvent {
Key key;
};
struct CharInputEvent {
uint32_t codepoint;
};
struct CursorPosEvent {
double x;
double y;
};
struct ScrollEvent {
double dx;
double dy;
};
enum MouseButton {
MOUSE_BUTTON_LEFT,
MOUSE_BUTTON_RIGHT,
MOUSE_BUTTON_MIDDLE
};
struct MouseEvent {
MouseButton button;
bool state;
double x;
double y;
};
}
class WindowImpl: public EventTarget, public ClientSubsystem {
public:
virtual bool shouldClose() = 0;
virtual WGPUSurface createWGPUSurface(WGPUInstance instance) = 0;
virtual void setPointerLock() = 0;
virtual void releasePointerLock() = 0;
virtual bool isKeyDown(Key key) = 0;
};
class Window: public WindowImpl {
private:
GLFWwindow * window = nullptr;
public:
Window(uint32_t width, uint32_t height, std::string title);
void render() override;
WGPUSurface createWGPUSurface(WGPUInstance instance) override;
void setTitle(std::string title);
bool shouldClose() override;
void setPointerLock() override;
void releasePointerLock() override;
bool isKeyDown(Key key) override;
};
}

1
Client/glfw3webgpu/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
releases/

View file

@ -0,0 +1,15 @@
# This is only meant to be included as a subdirectory in another project.
# It assumes that targets 'glfw' and 'webgpu' exist.
# Look at examples/CMakeLists.txt to see how to use it in a project.
# The glfw3webgpu target
add_library(glfw3webgpu STATIC glfw3webgpu.c)
target_include_directories(glfw3webgpu PUBLIC .)
target_link_libraries(glfw3webgpu PUBLIC glfw webgpu)
# Copy compile definitions that are PRIVATE in glfw
if (GLFW_BUILD_COCOA)
target_compile_definitions(glfw3webgpu PRIVATE _GLFW_COCOA)
target_compile_options(glfw3webgpu PRIVATE -x objective-c)
target_link_libraries(glfw3webgpu PRIVATE "-framework Cocoa" "-framework CoreVideo" "-framework IOKit" "-framework QuartzCore")
endif()

View file

@ -0,0 +1,20 @@
MIT License
Copyright (c) 2022-2024 Élie 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.

View file

@ -0,0 +1,94 @@
<img src="https://github.com/eliemichel/glfw3webgpu/actions/workflows/cmake.yml/badge.svg" alt="CMake Badge" />
<div align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/eliemichel/LearnWebGPU/main/images/webgpu-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/eliemichel/LearnWebGPU/main/images/webgpu-light.svg">
<img alt="Learn WebGPU Logo" src="images/webgpu-dark.svg" width="200">
</picture>
<a href="https://github.com/eliemichel/LearnWebGPU">LearnWebGPU</a> &nbsp;|&nbsp; <a href="https://github.com/eliemichel/WebGPU-Cpp">WebGPU-C++</a> &nbsp;|&nbsp; <a href="https://github.com/eliemichel/WebGPU-distribution">WebGPU-distribution</a><br/>
<a href="https://github.com/eliemichel/glfw3webgpu">glfw3webgpu</a> &nbsp;|&nbsp; <a href="https://github.com/eliemichel/sdl2webgpu">sdl2webgpu</a> &nbsp;|&nbsp; <a href="https://github.com/eliemichel/sdl3webgpu">sdl3webgpu</a>
<a href="https://discord.gg/2Tar4Kt564"><img src="https://img.shields.io/static/v1?label=Discord&message=Join%20us!&color=blue&logo=discord&logoColor=white" alt="Discord | Join us!"/></a>
</div>
GLFW WebGPU Extension
=====================
This is an extension for the great [GLFW](https://www.glfw.org/) library for using it with **WebGPU native**. It was written as part of the [Learn WebGPU for native C++](https://eliemichel.github.io/LearnWebGPU) tutorial series.
Table of Contents
-----------------
- [Overview](#overview)
- [Usage](#usage)
- [Example](#example)
- [License](#license)
Overview
--------
This extension simply provides the following function:
```C
WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window);
```
Given a GLFW window, `glfwCreateWindowWGPUSurface` returns a WebGPU *surface* that corresponds to the window's back-end. This is a process that is highly platform-specific, which is why I believe it belongs to GLFW.
Usage
-----
**NB** The current version of this extension is written for GLFW 3.4. Up to version 1.2.0, it was written for GLFW 3.3.8.
Your project must link to an implementation of WebGPU (providing `webgpu.h`) and of course to GLFW. Then:
**Option A** If you use CMake, you can simply include this project as a subdirectory with `add_subdirectory(glfw3webgpu)` (see the content of [`CMakeLists.txt`](CMakeLists.txt)).
**Option B** Just copy [`glfw3webgpu.h`](glfw3webgpu.h) and [`glfw3webgpu.c`](glfw3webgpu.c) to your project's source tree. On MacOS, you must add the compile option `-x objective-c` and the link libraries `-framework Cocoa`, `-framework CoreVideo`, `-framework IOKit`, and `-framework QuartzCore`.
Example
-------
Thanks to this extension it is possible to simply write a fully cross-platform WebGPU hello world:
```C
#include "glfw3webgpu.h"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <webgpu/webgpu.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
// Init WebGPU
WGPUInstanceDescriptor desc;
desc.nextInChain = NULL;
WGPUInstance instance = wgpuCreateInstance(&desc);
// Init GLFW
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* window = glfwCreateWindow(640, 480, "Learn WebGPU", NULL, NULL);
// Here we create our WebGPU surface from the window!
WGPUSurface surface = glfwCreateWindowWGPUSurface(instance, window);
printf("surface = %p", surface);
// Terminate GLFW
while (!glfwWindowShouldClose(window)) glfwPollEvents();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
```
**NB** The linking process depends on the implementation of WebGPU that you are using. You can find detailed instructions for the `wgpu-native` implementation in [this Hello WebGPU chapter](https://eliemichel.github.io/LearnWebGPU/getting-started/hello-webgpu.html). You may also check out [`examples/CMakeLists.txt`](examples/CMakeLists.txt).
License
-------
See [LICENSE.txt](LICENSE.txt).

View file

@ -0,0 +1,191 @@
/**
* This is an extension of GLFW 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-2025 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 "glfw3webgpu.h"
#include <webgpu/webgpu.h>
#include <GLFW/glfw3.h>
// Begin hack
#if defined(__APPLE__)
#define _GLFW_COCOA
#elif defined(_WIN32)
#define _GLFW_WIN32
#elif defined(__linux__)
#define _GLFW_X11 // or _GLFW_WAYLAND if you built GLFW with Wayland
#endif
// End hack
#ifdef __EMSCRIPTEN__
# define GLFW_EXPOSE_NATIVE_EMSCRIPTEN
# ifndef GLFW_PLATFORM_EMSCRIPTEN // not defined in older versions of emscripten
# define GLFW_PLATFORM_EMSCRIPTEN 0
# endif
#else // __EMSCRIPTEN__
# ifdef _GLFW_X11
# define GLFW_EXPOSE_NATIVE_X11
# endif
# ifdef _GLFW_WAYLAND
# define GLFW_EXPOSE_NATIVE_WAYLAND
# endif
# ifdef _GLFW_COCOA
# define GLFW_EXPOSE_NATIVE_COCOA
# endif
# ifdef _GLFW_WIN32
# define GLFW_EXPOSE_NATIVE_WIN32
# endif
#endif // __EMSCRIPTEN__
#ifdef GLFW_EXPOSE_NATIVE_COCOA
# include <Foundation/Foundation.h>
# include <QuartzCore/CAMetalLayer.h>
#endif
#ifndef __EMSCRIPTEN__
# include <GLFW/glfw3native.h>
#endif
WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window) {
#ifndef __EMSCRIPTEN__
switch (glfwGetPlatform()) {
#else
// glfwGetPlatform is not available in older versions of emscripten
switch (GLFW_PLATFORM_EMSCRIPTEN) {
#endif
#ifdef GLFW_EXPOSE_NATIVE_X11
case GLFW_PLATFORM_X11: {
Display* x11_display = glfwGetX11Display();
Window x11_window = glfwGetX11Window(window);
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);
}
#endif // GLFW_EXPOSE_NATIVE_X11
#ifdef GLFW_EXPOSE_NATIVE_WAYLAND
case GLFW_PLATFORM_WAYLAND: {
struct wl_display* wayland_display = glfwGetWaylandDisplay();
struct wl_surface* wayland_surface = glfwGetWaylandWindow(window);
WGPUSurfaceSourceWaylandSurface fromWaylandSurface;
fromWaylandSurface.chain.sType = WGPUSType_SurfaceSourceWaylandSurface;
fromWaylandSurface.chain.next = NULL;
fromWaylandSurface.display = wayland_display;
fromWaylandSurface.surface = wayland_surface;
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromWaylandSurface.chain;
surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN };
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
#endif // GLFW_EXPOSE_NATIVE_WAYLAND
#ifdef GLFW_EXPOSE_NATIVE_COCOA
case GLFW_PLATFORM_COCOA: {
id metal_layer = [CAMetalLayer layer];
NSWindow* ns_window = glfwGetCocoaWindow(window);
[ns_window.contentView setWantsLayer : YES] ;
[ns_window.contentView setLayer : metal_layer] ;
WGPUSurfaceSourceMetalLayer fromMetalLayer;
fromMetalLayer.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
fromMetalLayer.chain.next = NULL;
fromMetalLayer.layer = (__bridge void *)(metal_layer);
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromMetalLayer.chain;
surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN };
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
#endif // GLFW_EXPOSE_NATIVE_COCOA
#ifdef GLFW_EXPOSE_NATIVE_WIN32
case GLFW_PLATFORM_WIN32: {
HWND hwnd = glfwGetWin32Window(window);
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);
}
#endif // GLFW_EXPOSE_NATIVE_WIN32
#ifdef GLFW_EXPOSE_NATIVE_EMSCRIPTEN
case GLFW_PLATFORM_EMSCRIPTEN: {
# ifdef WEBGPU_BACKEND_EMDAWNWEBGPU
WGPUEmscriptenSurfaceSourceCanvasHTMLSelector fromCanvasHTMLSelector;
fromCanvasHTMLSelector.chain.sType = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector;
fromCanvasHTMLSelector.selector = (WGPUStringView){ "canvas", WGPU_STRLEN };
# else
WGPUSurfaceDescriptorFromCanvasHTMLSelector fromCanvasHTMLSelector;
fromCanvasHTMLSelector.chain.sType = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector;
fromCanvasHTMLSelector.selector = "canvas";
# endif
fromCanvasHTMLSelector.chain.next = NULL;
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromCanvasHTMLSelector.chain;
# ifdef WEBGPU_BACKEND_EMDAWNWEBGPU
surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN };
# else
surfaceDescriptor.label = NULL;
# endif
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
#endif // GLFW_EXPOSE_NATIVE_EMSCRIPTEN
default:
// Unsupported platform
return NULL;
}
}

View file

@ -0,0 +1,62 @@
/**
* This is an extension of GLFW 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 _glfw3_webgpu_h_
#define _glfw3_webgpu_h_
#include <webgpu/webgpu.h>
#include <GLFW/glfw3.h>
#ifdef __cplusplus
extern "C" {
#endif
/*! @brief Creates a WebGPU surface for the specified window.
*
* This function creates a WGPUSurface object for the specified window.
*
* If the surface cannot be created, this function returns `NULL`.
*
* It is the responsibility of the caller to destroy the window surface. The
* window surface must be destroyed using `wgpuSurfaceRelease`.
*
* @param[in] instance The WebGPU instance to create the surface in.
* @param[in] window The window to create the surface for.
* @return The handle of the surface. This is set to `NULL` if an error
* occurred.
*
* @ingroup webgpu
*/
WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window);
#ifdef __cplusplus
}
#endif
#endif // _glfw3_webgpu_h_