ArtifactEngine/src/core/Engine.cpp
2025-12-09 16:32:47 -05:00

805 lines
28 KiB
C++

#include <iostream>
#include <memory>
#include <glfw3webgpu.h>
#include <webgpu/webgpu.h>
#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 <gtc/matrix_transform.hpp>
#include <Engine.h>
#include <Events.h>
#include <Util.h>
#include <UI.h>
#include <ClientWorld.h>
#include <Server.h>
#define STEP_SIZE 1000
/*
namespace Artifact {
void Engine::init() {
instance = wgpuCreateInstance(nullptr);
surface = glfwCreateWindowWGPUSurface(instance, window);
}
void Engine::run() {
init();
while(!glfwWindowShouldClose(window)) {
glfwPollEvents();
}
}
Engine::Engine() {
world = std::make_unique<World::World>();
}
Engine::~Engine() {
std::cout << "Destructing engine..." << std::endl;
}
}
/*/
static struct _glfwInit {
_glfwInit() {
if(!glfwInit()) printf("Failed to initialize GLFW.");
}
~_glfwInit() {
glfwTerminate();
}
} __glfwinit;
namespace Artifact {
Engine::Engine() {
world = std::make_unique<World::ClientWorld>();
}
Engine::~Engine() {
std::cout << "Destructing engine..." << std::endl;
}
void Engine::init() {
instance = wgpuCreateInstance(nullptr);
surface = window.createWGPUSurface(instance);
if (!surface) {
std::cerr << "Failed to create surface" << std::endl;
return;
}
WGPURequestAdapterCallbackInfo adapterCallbackOpts = {};
adapterCallbackOpts.callback = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView error, void* engine, void*) {
if(status == WGPURequestAdapterStatus_Success) {
static_cast<Engine*>(engine)->adapter = adapter;
}
};
adapterCallbackOpts.mode = WGPUCallbackMode_AllowProcessEvents;
adapterCallbackOpts.userdata1 = this;
WGPURequestAdapterOptions adapterOpts = {};
adapterOpts.compatibleSurface = surface;
wgpuInstanceRequestAdapter(instance, &adapterOpts, adapterCallbackOpts);
while(!adapter) wgpuInstanceProcessEvents(instance);
WGPURequestDeviceCallbackInfo deviceCallbackOpts = {};
deviceCallbackOpts.callback = [](WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView error, void* engine, void*) {
if(status == WGPURequestDeviceStatus_Success) {
static_cast<Engine*>(engine)->device = device;
} else {
std::cerr << error.data << std::endl;
}
};
deviceCallbackOpts.mode = WGPUCallbackMode_AllowProcessEvents;
deviceCallbackOpts.userdata1 = this;
WGPUUncapturedErrorCallbackInfo errorCallbackInfo = {};
errorCallbackInfo.callback = [](auto device, WGPUErrorType type, WGPUStringView message, void*, void*) {
const char* msg = (message.data ? reinterpret_cast<const char*>(message.data) : "No message");
fprintf(stderr, "Uncaptured WebGPU Error (type %d): %s\n", type, msg);
if (type == WGPUErrorType_Validation) {
// Optional: Break into debugger or abort for inspection
__builtin_trap();
}
};
errorCallbackInfo.userdata1 = nullptr; // Optional user data
WGPUDeviceDescriptor deviceDesc = {};
deviceDesc.uncapturedErrorCallbackInfo = errorCallbackInfo;
wgpuAdapterRequestDevice(adapter, nullptr, deviceCallbackOpts);
while(!device) wgpuInstanceProcessEvents(instance);
queue = wgpuDeviceGetQueue(device);
WGPUSurfaceConfiguration surfaceConfig = {};
surfaceConfig.device = device;
surfaceConfig.format = surfaceFormat;
surfaceConfig.usage = WGPUTextureUsage_RenderAttachment;
surfaceConfig.width = viewportWidth;
surfaceConfig.height = viewportHeight;
surfaceConfig.presentMode = WGPUPresentMode_Fifo;
wgpuSurfaceConfigure(surface, &surfaceConfig);
WGPUTextureDescriptor depthDesc = {};
depthDesc.dimension = WGPUTextureDimension_2D;
depthDesc.size.width = viewportWidth;
depthDesc.size.height = viewportHeight;
depthDesc.size.depthOrArrayLayers = 1;
depthDesc.format = WGPUTextureFormat_Depth24Plus;
depthDesc.usage = WGPUTextureUsage_RenderAttachment;
depthDesc.mipLevelCount = 1;
depthDesc.sampleCount = 1;
depthTexture = std::make_unique<Util::Texture>();
depthTexture->texture = wgpuDeviceCreateTexture(device, &depthDesc);
depthTexture->view = wgpuTextureCreateView(depthTexture->texture, nullptr);
std::cout << "Initializing Nuklear..." << std::endl;
// Initialize Nuklear
ui = std::make_unique<nk_context>();
nk_init_default(ui.get(), nullptr);
nk_font_atlas* atlas = (nk_font_atlas*)malloc(sizeof(nk_font_atlas));
nk_font_atlas_init_default(atlas);
nk_font_atlas_begin(atlas);
std::cout << "Loading font..." << std::endl;
nk_font* font;
void* fontData = nullptr;
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 {
std::cerr << "Failed to load font: " << (assetPath + "/fonts/Arial.ttf") << std::endl;
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);
uiFontTexture = Util::createTextureFromData(device, queue, fontImage, fontWidth, fontHeight);
nk_font_atlas_end(atlas, nk_handle_id(0), nullptr);
nk_style_set_font(ui.get(), &font->handle);
if (fontData) free(fontData);
free(atlas);
unsigned char whitePixel[4] = {255, 255, 255, 255};
uiDummyTexture = Util::createTextureFromData(device, queue, whitePixel, 1, 1);
nk_buffer_init_default(&uiVertexBufferNK);
nk_buffer_init_default(&uiIndexBufferNK);
nk_buffer_init_default(&uiCommandBufferNK);
auto window_ = &window;
auto ui_ = ui.get();
window.listen<Events::Key>([window_, ui_](auto ev) {
bool press = ev.action == GLFW_PRESS;
switch (ev.key) {
case GLFW_KEY_ESCAPE:
window_->close();
break;
case GLFW_KEY_DELETE:
nk_input_key(ui_, NK_KEY_DEL, press);
break;
case GLFW_KEY_ENTER:
nk_input_key(ui_, NK_KEY_ENTER, press);
break;
case GLFW_KEY_TAB:
nk_input_key(ui_, NK_KEY_TAB, press);
break;
case GLFW_KEY_BACKSPACE:
nk_input_key(ui_, NK_KEY_BACKSPACE, press);
break;
case GLFW_KEY_UP:
nk_input_key(ui_, NK_KEY_UP, press);
break;
case GLFW_KEY_DOWN:
nk_input_key(ui_, NK_KEY_DOWN, press);
break;
case GLFW_KEY_HOME:
nk_input_key(ui_, NK_KEY_TEXT_START, press);
nk_input_key(ui_, NK_KEY_SCROLL_START, press);
break;
case GLFW_KEY_END:
nk_input_key(ui_, NK_KEY_TEXT_END, press);
nk_input_key(ui_, NK_KEY_SCROLL_END, press);
break;
case GLFW_KEY_PAGE_DOWN:
nk_input_key(ui_, NK_KEY_SCROLL_DOWN, press);
break;
case GLFW_KEY_PAGE_UP:
nk_input_key(ui_, NK_KEY_SCROLL_UP, press);
break;
case GLFW_KEY_LEFT_SHIFT:
case GLFW_KEY_RIGHT_SHIFT:
nk_input_key(ui_, NK_KEY_SHIFT, press);
break;
// case GLFW_KEY_LEFT_CONTROL:
// case GLFW_KEY_RIGHT_CONTROL:
// if (press) {
// nk_input_key(ui.get(), NK_KEY_COPY, glfwGetKey(window, GLFW_KEY_C) == GLFW_PRESS);
// nk_input_key(ui.get(), NK_KEY_PASTE, glfwGetKey(window, GLFW_KEY_P) == GLFW_PRESS);
// nk_input_key(ui.get(), NK_KEY_CUT, glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS);
// nk_input_key(ui.get(), NK_KEY_TEXT_UNDO, glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS);
// nk_input_key(ui.get(), NK_KEY_TEXT_REDO, glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS);
// nk_input_key(ui.get(), NK_KEY_TEXT_WORD_LEFT, glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS);
// nk_input_key(ui.get(), NK_KEY_TEXT_WORD_RIGHT, glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS);
// nk_input_key(ui.get(), NK_KEY_TEXT_LINE_START, glfwGetKey(window, GLFW_KEY_B) == GLFW_PRESS);
// nk_input_key(ui.get(), NK_KEY_TEXT_LINE_END, glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS);
// } else {
// nk_input_key(ui.get(), NK_KEY_LEFT, glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS);
// nk_input_key(ui.get(), NK_KEY_RIGHT, glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS);
// nk_input_key(ui.get(), NK_KEY_COPY, false);
// nk_input_key(ui.get(), NK_KEY_PASTE, false);
// nk_input_key(ui.get(), NK_KEY_CUT, false);
// nk_input_key(ui.get(), NK_KEY_SHIFT, false);
// }
// break;
}
});
window.listen<Events::CursorPos>([this](auto ev) {
nk_input_motion(ui.get(), ev.x, ev.y);
});
window.listen<Events::Action>([this](auto ev) {
nk_buttons type;
switch(ev.type) {
case Events::ActionType::PRIMARY:
type = NK_BUTTON_LEFT;
break;
case Events::ActionType::SECONDARY:
type = NK_BUTTON_RIGHT;
break;
case Events::ActionType::TERTIARY:
type = NK_BUTTON_MIDDLE;
break;
default:
return;
}
nk_input_button(ui.get(), type, (int)ev.x, (int)ev.y, ev.state);
});
int atlasWidth, atlasHeight;
std::vector<unsigned char> atlasData;
if (!world->nodeRegistry.generateTextureAtlas(atlasWidth, atlasHeight, atlasData)) {
std::cerr << "Failed to generate texture atlas\n";
return;
}
std::cout << "Creating texture atlas..." << std::endl;
nodeTextureAtlas = Util::createTextureFromData(device, queue, atlasData.data(), atlasWidth, atlasHeight);
if (!nodeTextureAtlas->texture || !nodeTextureAtlas->view) {
std::cerr << "Failed to load texture atlas\n";
return;
}
std::cout << "Texture atlas created." << std::endl;
// MARK: Setup pipelines
reloadShaders();
dispatch(Events::Initialized{});
}
void Engine::reloadShaders() {
makeWorldPipelines();
makeObjectPipeline();
makeUIPipeline();
dispatch(Events::PipelineReload{});
}
void Engine::makeWorldPipelines() {
WGPUBufferDescriptor uniformBufferDesc = {};
uniformBufferDesc.size = sizeof(glm::mat4);
uniformBufferDesc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst;
worldUniformBuffer = wgpuDeviceCreateBuffer(device, &uniformBufferDesc);
if (!worldUniformBuffer) {
std::cerr << "Failed to create uniform buffer" << std::endl;
return;
}
WGPUSamplerDescriptor samplerDesc = {};
samplerDesc.addressModeU = WGPUAddressMode_Repeat;
samplerDesc.addressModeV = WGPUAddressMode_Repeat;
samplerDesc.addressModeW = WGPUAddressMode_Repeat;
samplerDesc.magFilter = WGPUFilterMode_Nearest;
samplerDesc.minFilter = WGPUFilterMode_Nearest;
samplerDesc.mipmapFilter = WGPUMipmapFilterMode_Linear;
samplerDesc.maxAnisotropy = 1;
worldSampler = wgpuDeviceCreateSampler(device, &samplerDesc);
if (!worldSampler) {
std::cerr << "Failed to create sampler" << std::endl;
return;
}
WGPUBindGroupLayoutEntry bglEntries[3] = {};
bglEntries[0].binding = 0;
bglEntries[0].visibility = WGPUShaderStage_Vertex;
bglEntries[0].buffer.type = WGPUBufferBindingType_Uniform;
bglEntries[1].binding = 1;
bglEntries[1].visibility = WGPUShaderStage_Fragment;
bglEntries[1].texture.sampleType = WGPUTextureSampleType_Float;
bglEntries[1].texture.viewDimension = WGPUTextureViewDimension_2D;
bglEntries[2].binding = 2;
bglEntries[2].visibility = WGPUShaderStage_Fragment;
bglEntries[2].sampler.type = WGPUSamplerBindingType_Filtering;
WGPUBindGroupLayoutDescriptor bglDesc = {};
bglDesc.entryCount = 3;
bglDesc.entries = bglEntries;
worldBgl = wgpuDeviceCreateBindGroupLayout(device, &bglDesc);
if (!worldBgl) {
std::cerr << "Failed to create bind group layout" << std::endl;
return;
}
WGPUBindGroupEntry bgEntries[3] = {};
bgEntries[0].binding = 0;
bgEntries[0].buffer = worldUniformBuffer;
bgEntries[0].offset = 0;
bgEntries[0].size = sizeof(glm::mat4);
bgEntries[1].binding = 1;
bgEntries[1].textureView = nodeTextureAtlas->view;
bgEntries[2].binding = 2;
bgEntries[2].sampler = worldSampler;
WGPUBindGroupDescriptor bgDesc = {};
bgDesc.layout = worldBgl;
bgDesc.entryCount = 3;
bgDesc.entries = bgEntries;
worldBindGroup = wgpuDeviceCreateBindGroup(device, &bgDesc);
if (!worldBindGroup) {
std::cerr << "Failed to create cube bind group" << std::endl;
return;
}
const char* cubeVSCode = R"(
struct Uniforms {
mvp: mat4x4<f32>,
}
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
struct VertexOutput {
@builtin(position) pos: vec4<f32>,
@location(0) uv: vec2<f32>
}
@vertex
fn vs_main(
@location(0) pos: vec3<f32>,
@location(1) uv: vec2<f32>
) -> VertexOutput {
var out: VertexOutput;
out.pos = uniforms.mvp * vec4<f32>(pos, 1.0);
out.uv = uv;
return out;
}
)";
const char* cubeFSCode = 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(0) vec4<f32> {
var color = textureSample(texture, sampler_, uv);
return color;
}
)";
WGPUShaderModuleDescriptor cubeVSDesc = {};
WGPUShaderSourceWGSL src = {.code = WGPUStringView{cubeVSCode, WGPU_STRLEN}};
src.chain.sType = WGPUSType_ShaderSourceWGSL;
cubeVSDesc.nextInChain = &src.chain;
WGPUShaderModule cubeVSModule = wgpuDeviceCreateShaderModule(device, &cubeVSDesc);
WGPUShaderModuleDescriptor cubeFSDesc = {};
src = {.code = WGPUStringView{cubeFSCode, WGPU_STRLEN}};
src.chain.sType = WGPUSType_ShaderSourceWGSL;
cubeFSDesc.nextInChain = &src.chain;
WGPUShaderModule cubeFSModule = wgpuDeviceCreateShaderModule(device, &cubeFSDesc);
WGPUVertexAttribute cubeAttributes[2] = {};
cubeAttributes[0].format = WGPUVertexFormat_Float32x3;
cubeAttributes[0].offset = offsetof(Vertex, pos);
cubeAttributes[0].shaderLocation = 0;
cubeAttributes[1].format = WGPUVertexFormat_Float32x2;
cubeAttributes[1].offset = offsetof(Vertex, uv);
cubeAttributes[1].shaderLocation = 1;
WGPUVertexBufferLayout cubeVBLayout = {};
cubeVBLayout.arrayStride = sizeof(Vertex);
cubeVBLayout.stepMode = WGPUVertexStepMode_Vertex;
cubeVBLayout.attributeCount = 2;
cubeVBLayout.attributes = cubeAttributes;
WGPUPipelineLayoutDescriptor plDesc = {};
plDesc.bindGroupLayoutCount = 1;
plDesc.bindGroupLayouts = &worldBgl;
WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(device, &plDesc);
WGPUColorTargetState cubeColorTarget = {};
cubeColorTarget.format = surfaceFormat;
cubeColorTarget.writeMask = WGPUColorWriteMask_All;
WGPUFragmentState cubeFragmentState = {};
cubeFragmentState.module = cubeFSModule;
cubeFragmentState.entryPoint = WGPUStringView{"fs_main", WGPU_STRLEN};
cubeFragmentState.targetCount = 1;
cubeFragmentState.targets = &cubeColorTarget;
WGPUDepthStencilState depthStencilState = {};
depthStencilState.format = WGPUTextureFormat_Depth24Plus;
depthStencilState.depthWriteEnabled = WGPUOptionalBool_True;
depthStencilState.depthCompare = WGPUCompareFunction_Less;
WGPURenderPipelineDescriptor cubePipelineDesc = {};
cubePipelineDesc.vertex.module = cubeVSModule;
cubePipelineDesc.vertex.entryPoint = WGPUStringView{"vs_main", WGPU_STRLEN};
cubePipelineDesc.vertex.bufferCount = 1;
cubePipelineDesc.vertex.buffers = &cubeVBLayout;
cubePipelineDesc.fragment = &cubeFragmentState;
cubePipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
cubePipelineDesc.primitive.frontFace = WGPUFrontFace_CCW;
cubePipelineDesc.primitive.cullMode = WGPUCullMode_None;
cubePipelineDesc.depthStencil = &depthStencilState;
cubePipelineDesc.layout = pipelineLayout;
cubePipelineDesc.multisample.count = 1;
cubePipelineDesc.multisample.mask = ~0u;
worldPipeline = wgpuDeviceCreateRenderPipeline(device, &cubePipelineDesc);
std::cout << "Created world pipeline." << std::endl;
}
void Engine::makeObjectPipeline() {
}
void Engine::makeUIPipeline() {
WGPUBufferDescriptor uiVertexBufferDesc = {};
uiVertexBufferDesc.size = 1024 * 1024 * sizeof(NKVertex);
uiVertexBufferDesc.usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst;
uiVertexBufferDesc.label = WGPUStringView{"UI Vertex Buffer", WGPU_STRLEN};
uiVertexBuffer = wgpuDeviceCreateBuffer(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};
uiIndexBuffer = wgpuDeviceCreateBuffer(device, &uiIndexBufferDesc);
WGPUBufferDescriptor uiUniformBufferDesc = {};
uiUniformBufferDesc.size = sizeof(glm::mat4);
uiUniformBufferDesc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst;
uiUniformBufferDesc.label = WGPUStringView{"UI Uniform Buffer", WGPU_STRLEN};
uiUniformBuffer = wgpuDeviceCreateBuffer(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(device, &uiVSDesc);
WGPUShaderModuleDescriptor uiFSDesc = {};
src = {.code = WGPUStringView{uiFSCode.c_str(), uiFSCode.size()}};
src.chain.sType = WGPUSType_ShaderSourceWGSL;
uiFSDesc.nextInChain = &src.chain;
WGPUShaderModule uiFSModule = wgpuDeviceCreateShaderModule(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;
uiSampler = wgpuDeviceCreateSampler(device, &uiSamplerDesc);
if (!uiSampler) {
std::cerr << "Failed to create UI sampler" << std::endl;
return;
}
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;
uiBgl = wgpuDeviceCreateBindGroupLayout(device, &uiBglDesc);
if (!uiBgl) {
std::cerr << "Failed to create bind group layout" << std::endl;
return;
}
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 = surfaceFormat;
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 = &uiBgl;
WGPUPipelineLayout uiPipelineLayout = wgpuDeviceCreatePipelineLayout(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;
uiPipeline = wgpuDeviceCreateRenderPipeline(device, &uiPipelineDesc);
}
// Default main loop handler.
void Engine::run() {
init();
time_t last_time;
time(&last_time);
while(!window.shouldClose()) {
nk_input_begin(ui.get());
glfwPollEvents();
nk_input_end(ui.get());
render();
time_t now;
time(&now);
if(now - last_time > STEP_SIZE) {
tick();
dispatch(Events::Tick{now - last_time});
last_time = now;
}
}
}
void Engine::render() {
dispatch(Events::DrawUI{ui.get()});
nk_buffer_clear(&uiVertexBufferNK);
nk_buffer_clear(&uiIndexBufferNK);
nk_buffer_clear(&uiCommandBufferNK);
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(ui.get(), &uiCommandBufferNK, &uiVertexBufferNK, &uiIndexBufferNK, &config);
ortho = glm::ortho(0.0f, (float)viewportWidth, (float)viewportHeight, 0.0f, -1.0f, 1.0f);
wgpuQueueWriteBuffer(queue, uiUniformBuffer, 0, &ortho, sizeof(ortho));
WGPUSurfaceTexture surfaceTexture;
wgpuSurfaceGetCurrentTexture(surface, &surfaceTexture);
if (surfaceTexture.status == WGPUSurfaceGetCurrentTextureStatus_Error || !surfaceTexture.texture) {
std::cerr << "Failed to get surface texture" << std::endl;
return;
}
WGPUTextureView nextTexture = wgpuTextureCreateView(surfaceTexture.texture, nullptr);
if (!nextTexture) {
std::cerr << "Failed to create texture view" << std::endl;
return;
}
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, nullptr);
for(const auto &pair : world->chunks) {
chunkRenderer->render(pair.first, pair.second.get(), encoder, nextTexture);
}
WGPURenderPassColorAttachment colorAttachment = {};
colorAttachment.view = nextTexture;
colorAttachment.loadOp = WGPULoadOp_Clear;
colorAttachment.storeOp = WGPUStoreOp_Store;
colorAttachment.clearValue = {0, 0.2, 0.4, 1};
colorAttachment.depthSlice = -1;
WGPURenderPassDepthStencilAttachment depthAttachment = {};
depthAttachment.view = depthTexture->view;
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, uiPipeline);
wgpuRenderPassEncoderSetViewport(uiRenderPass, 0, 0, (float)viewportWidth, (float)viewportHeight, 0.0f, 1.0f);
size_t vertexSize = nk_buffer_total(&uiVertexBufferNK);
size_t indexSize = nk_buffer_total(&uiIndexBufferNK);
if (vertexSize > 0 && indexSize > 0) {
wgpuQueueWriteBuffer(queue, uiVertexBuffer, 0, uiVertexBufferNK.memory.ptr, vertexSize);
wgpuQueueWriteBuffer(queue, uiIndexBuffer, 0, uiIndexBufferNK.memory.ptr, indexSize);
wgpuRenderPassEncoderSetVertexBuffer(uiRenderPass, 0, uiVertexBuffer, 0, vertexSize);
wgpuRenderPassEncoderSetIndexBuffer(uiRenderPass, uiIndexBuffer, WGPUIndexFormat_Uint16, 0, indexSize);
std::vector<WGPUBindGroup> bindGroups;
WGPUBindGroupEntry uiBgEntries[3] = {};
uiBgEntries[0].binding = 0;
uiBgEntries[0].buffer = uiUniformBuffer;
uiBgEntries[0].offset = 0;
uiBgEntries[0].size = sizeof(glm::mat4);
uiBgEntries[2].binding = 2;
uiBgEntries[2].sampler = uiSampler;
std::map<int, WGPUTextureView> textureMap = {{0, uiFontTexture->view}};
const struct nk_draw_command* cmd;
uint32_t offset = 0;
nk_draw_foreach(cmd, ui.get(), &uiCommandBufferNK) {
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] : uiDummyTexture->view;
WGPUBindGroupDescriptor uiBgDesc = {};
uiBgDesc.layout = uiBgl;
uiBgDesc.entryCount = 3;
uiBgDesc.entries = uiBgEntries;
WGPUBindGroup uiDynamicBindGroup = wgpuDeviceCreateBindGroup(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);
}
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);
nk_clear(ui.get());
}
void Engine::tick() {
if(server) server->tick();
}
}
//*/