Initial commit.
This commit is contained in:
commit
d99b70eaa1
33 changed files with 2291 additions and 0 deletions
BIN
assets/fonts/Arial.ttf
Normal file
BIN
assets/fonts/Arial.ttf
Normal file
Binary file not shown.
BIN
assets/test_stone.png
Normal file
BIN
assets/test_stone.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 221 B |
42
src/client/ChunkRenderer.cpp
Normal file
42
src/client/ChunkRenderer.cpp
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
#include <Engine.h>
|
||||||
|
#include <ChunkRenderer.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
void ChunkRenderer::render(glm::ivec3 pos, World::Chunk* chunk, WGPUCommandEncoder encoder, WGPUTextureView texture) {
|
||||||
|
WGPURenderPassColorAttachment colorAttachment = {};
|
||||||
|
colorAttachment.view = texture;
|
||||||
|
colorAttachment.loadOp = WGPULoadOp_Clear;
|
||||||
|
colorAttachment.storeOp = WGPUStoreOp_Store;
|
||||||
|
colorAttachment.clearValue = {0, 0, 0, 1};
|
||||||
|
colorAttachment.depthSlice = -1;
|
||||||
|
|
||||||
|
WGPURenderPassDepthStencilAttachment depthAttachment = {};
|
||||||
|
depthAttachment.view = engine->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;
|
||||||
|
|
||||||
|
WGPURenderPassEncoder cubeRenderPass = wgpuCommandEncoderBeginRenderPass(encoder, &renderPassDesc);
|
||||||
|
wgpuRenderPassEncoderSetPipeline(cubeRenderPass, engine->worldPipeline);
|
||||||
|
wgpuRenderPassEncoderSetViewport(cubeRenderPass, 0, 0, (float)engine->viewportWidth, (float)engine->viewportHeight, 0.0f, 1.0f);
|
||||||
|
wgpuRenderPassEncoderSetBindGroup(cubeRenderPass, 0, engine->worldBindGroup, 0, nullptr);
|
||||||
|
for (const auto &pair : chunkBuffers) {
|
||||||
|
const ChunkRenderData &data = pair.second;
|
||||||
|
wgpuRenderPassEncoderSetVertexBuffer(cubeRenderPass, 0, data.vertices, 0, data.vertexCount * sizeof(Vertex));
|
||||||
|
wgpuRenderPassEncoderSetIndexBuffer(cubeRenderPass, data.indices, WGPUIndexFormat_Uint32, 0, data.indexCount * sizeof(uint32_t));
|
||||||
|
wgpuRenderPassEncoderDrawIndexed(cubeRenderPass, data.indexCount, 1, 0, 0, 0);
|
||||||
|
}
|
||||||
|
wgpuRenderPassEncoderEnd(cubeRenderPass);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
37
src/client/ChunkRenderer.h
Normal file
37
src/client/ChunkRenderer.h
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
#include <webgpu/webgpu.h>
|
||||||
|
|
||||||
|
#include <Chunk.h>
|
||||||
|
#include <Mesh.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
namespace World {
|
||||||
|
|
||||||
|
class ClientChunk: public Chunk {
|
||||||
|
WGPUBuffer vertices;
|
||||||
|
WGPUBuffer indices;
|
||||||
|
|
||||||
|
void generateMesh() {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Engine;
|
||||||
|
|
||||||
|
class ChunkRenderer {
|
||||||
|
struct ChunkRenderData {
|
||||||
|
WGPUBuffer vertices;
|
||||||
|
WGPUBuffer indices;
|
||||||
|
int vertexCount;
|
||||||
|
int indexCount;
|
||||||
|
};
|
||||||
|
Engine* engine = nullptr;
|
||||||
|
std::unordered_map<glm::vec3, ChunkRenderData, Util::Vec3Hash, Util::Vec3Equal> chunkBuffers;
|
||||||
|
public:
|
||||||
|
ChunkRenderer(Engine* engine) : engine(engine) {}
|
||||||
|
void render(glm::ivec3 pos, World::Chunk* chunk, WGPUCommandEncoder encoder, WGPUTextureView texture);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
108
src/client/ClientWorld.cpp
Normal file
108
src/client/ClientWorld.cpp
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <webgpu/webgpu.h>
|
||||||
|
|
||||||
|
#include <ClientWorld.h>
|
||||||
|
#include <Chunk.h>
|
||||||
|
#include <Mesh.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
namespace World {
|
||||||
|
|
||||||
|
void ClientWorld::generateChunkMesh(WGPUDevice device, WGPUQueue queue, Chunk* chunk, ChunkMesh &mesh) {
|
||||||
|
std::vector<Vertex> verts;
|
||||||
|
std::vector<uint32_t> inds;
|
||||||
|
|
||||||
|
verts.reserve(24 * 1024); // rough upper bound
|
||||||
|
inds.reserve (36 * 1024);
|
||||||
|
|
||||||
|
auto emitQuad = [&](float x0, float y0, float z0,
|
||||||
|
float x1, float y1, float z1,
|
||||||
|
float x2, float y2, float z2,
|
||||||
|
float x3, float y3, float z3,
|
||||||
|
float u0, float v0, float u1, float v1) {
|
||||||
|
uint32_t base = static_cast<uint32_t>(verts.size());
|
||||||
|
verts.push_back({{x0,y0,z0}, {u0,v0}});
|
||||||
|
verts.push_back({{x1,y1,z1}, {u0,v1}});
|
||||||
|
verts.push_back({{x2,y2,z2}, {u1,v1}});
|
||||||
|
verts.push_back({{x3,y3,z3}, {u1,v0}});
|
||||||
|
|
||||||
|
inds.push_back(base + 0); inds.push_back(base + 1); inds.push_back(base + 2);
|
||||||
|
inds.push_back(base + 0); inds.push_back(base + 2); inds.push_back(base + 3);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int x = 0; x < CHUNK_SIZE; ++x) {
|
||||||
|
for (int y = 0; y < CHUNK_SIZE; ++y) {
|
||||||
|
for (int z = 0; z < CHUNK_SIZE; ++z) {
|
||||||
|
uint16_t id = chunk->getNodeRaw(x, y, z);
|
||||||
|
if (id == 0) continue; // air
|
||||||
|
|
||||||
|
float fx0 = static_cast<float>(x);
|
||||||
|
float fy0 = static_cast<float>(y);
|
||||||
|
float fz0 = static_cast<float>(z);
|
||||||
|
float fx1 = fx0 + 1.0f;
|
||||||
|
float fy1 = fy0 + 1.0f;
|
||||||
|
float fz1 = fz0 + 1.0f;
|
||||||
|
|
||||||
|
// +X
|
||||||
|
if (chunk->getNodeRaw(x+1, y, z) == 0) {
|
||||||
|
emitQuad(fx1,fy0,fz0, fx1,fy1,fz0, fx1,fy1,fz1, fx1,fy0,fz1, 0,0, 1,1);
|
||||||
|
}
|
||||||
|
// -X
|
||||||
|
if (chunk->getNodeRaw(x-1, y, z) == 0) {
|
||||||
|
emitQuad(fx0,fy0,fz1, fx0,fy1,fz1, fx0,fy1,fz0, fx0,fy0,fz0, 0,0, 1,1);
|
||||||
|
}
|
||||||
|
// +Y (top)
|
||||||
|
if (chunk->getNodeRaw(x, y+1, z) == 0) {
|
||||||
|
emitQuad(fx0,fy1,fz0, fx1,fy1,fz0, fx1,fy1,fz1, fx0,fy1,fz1, 0,0, 1,1);
|
||||||
|
}
|
||||||
|
// -Y (bottom)
|
||||||
|
if (chunk->getNodeRaw(x, y-1, z) == 0) {
|
||||||
|
emitQuad(fx0,fy0,fz1, fx1,fy0,fz1, fx1,fy0,fz0, fx0,fy0,fz0, 0,0, 1,1);
|
||||||
|
}
|
||||||
|
// +Z
|
||||||
|
if (chunk->getNodeRaw(x, y, z+1) == 0) {
|
||||||
|
emitQuad(fx0,fy0,fz1, fx0,fy1,fz1, fx1,fy1,fz1, fx1,fy0,fz1, 0,0, 1,1);
|
||||||
|
}
|
||||||
|
// -Z
|
||||||
|
if (chunk->getNodeRaw(x, y, z-1) == 0) {
|
||||||
|
emitQuad(fx1,fy0,fz0, fx1,fy1,fz0, fx0,fy1,fz0, fx0,fy0,fz0, 0,0, 1,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto isEmpty = inds.empty();
|
||||||
|
// chunk.indexCount = static_cast<uint32_t>(inds.size());
|
||||||
|
|
||||||
|
if (isEmpty) {
|
||||||
|
// Clean up old buffers if any
|
||||||
|
if (mesh.vertices) wgpuBufferDestroy(mesh.vertices);
|
||||||
|
if (mesh.indices) wgpuBufferDestroy(mesh.indices);
|
||||||
|
mesh.vertices = nullptr;
|
||||||
|
mesh.indices = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// (Re)create GPU buffers with exact size and copy data in one go
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
WGPUBufferDescriptor vdesc{};
|
||||||
|
vdesc.size = verts.size() * sizeof(Vertex);
|
||||||
|
vdesc.usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst;
|
||||||
|
mesh.vertices = wgpuDeviceCreateBuffer(device, &vdesc);
|
||||||
|
wgpuQueueWriteBuffer(queue, mesh.vertices, 0, verts.data(), vdesc.size);
|
||||||
|
|
||||||
|
WGPUBufferDescriptor idesc{};
|
||||||
|
idesc.size = inds.size() * sizeof(uint32_t);
|
||||||
|
idesc.usage = WGPUBufferUsage_Index | WGPUBufferUsage_CopyDst;
|
||||||
|
mesh.indices = wgpuDeviceCreateBuffer(device, &idesc);
|
||||||
|
wgpuQueueWriteBuffer(queue, mesh.indices, 0, inds.data(), idesc.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientWorld::render(WGPUDevice device, WGPUQueue queue, WGPUCommandEncoder encoder) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/client/ClientWorld.h
Normal file
20
src/client/ClientWorld.h
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
#include <World.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
namespace World {
|
||||||
|
|
||||||
|
struct ChunkMesh {
|
||||||
|
WGPUBuffer vertices = nullptr;
|
||||||
|
WGPUBuffer indices = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ClientWorld: public World::World {
|
||||||
|
public:
|
||||||
|
void generateChunkMesh(WGPUDevice device, WGPUQueue queue, Chunk* chunk, ChunkMesh &mesh);
|
||||||
|
void render(WGPUDevice device, WGPUQueue queue, WGPUCommandEncoder encoder);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
805
src/core/Engine.cpp
Normal file
805
src/core/Engine.cpp
Normal file
|
|
@ -0,0 +1,805 @@
|
||||||
|
#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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//*/
|
||||||
173
src/core/Engine.h
Normal file
173
src/core/Engine.h
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
#pragma once
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <glfw3webgpu.h>
|
||||||
|
#include <GLFW/glfw3.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 <cista.h>
|
||||||
|
|
||||||
|
#include <Window.h>
|
||||||
|
#include <Events.h>
|
||||||
|
#include <ClientWorld.h>
|
||||||
|
#include <ChunkRenderer.h>
|
||||||
|
#include <Texture.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
namespace Events {
|
||||||
|
struct PipelineReload {};
|
||||||
|
struct DrawUI {
|
||||||
|
nk_context* ctx;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
class Engine: public EventTarget {
|
||||||
|
public:
|
||||||
|
// Windowing
|
||||||
|
int viewportWidth = 1200;
|
||||||
|
int viewportHeight = 800;
|
||||||
|
GLFWwindow* window = []{
|
||||||
|
glfwInit();
|
||||||
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||||
|
auto w = glfwCreateWindow(1200, 800, "Artifact Engine", nullptr, nullptr);
|
||||||
|
if (!w) throw std::runtime_error("Failed to create GLFW window");
|
||||||
|
|
||||||
|
glfwShowWindow(w);
|
||||||
|
glfwPollEvents();
|
||||||
|
return w;
|
||||||
|
}();
|
||||||
|
|
||||||
|
WGPUInstance instance = nullptr;
|
||||||
|
WGPUAdapter adapter = nullptr;
|
||||||
|
WGPUDevice device = nullptr;
|
||||||
|
WGPUSurface surface = nullptr;
|
||||||
|
WGPUTextureFormat surfaceFormat = WGPUTextureFormat_BGRA8Unorm;
|
||||||
|
WGPUQueue queue = nullptr;
|
||||||
|
WGPURenderPipeline worldPipeline = nullptr;
|
||||||
|
WGPURenderPipeline objectPipeline = nullptr;
|
||||||
|
WGPURenderPipeline uiPipeline = nullptr;
|
||||||
|
std::unique_ptr<Util::Texture> depthTexture;
|
||||||
|
|
||||||
|
std::unique_ptr<World::World> world;
|
||||||
|
|
||||||
|
std::string assetPath = std::string(getenv("HOME")) + "/eclipse-workspace/ArtifactEngine/assets";
|
||||||
|
|
||||||
|
Engine();
|
||||||
|
~Engine();
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void run();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*/
|
||||||
|
|
||||||
|
// Forward declaration.
|
||||||
|
class Server;
|
||||||
|
|
||||||
|
class Engine: public EventTarget {
|
||||||
|
public:
|
||||||
|
// Windowing
|
||||||
|
int viewportWidth = 1200;
|
||||||
|
int viewportHeight = 800;
|
||||||
|
Window window;
|
||||||
|
|
||||||
|
// WebGPU
|
||||||
|
WGPUInstance instance = nullptr;
|
||||||
|
WGPUAdapter adapter = nullptr;
|
||||||
|
WGPUDevice device = nullptr;
|
||||||
|
WGPUSurface surface = nullptr;
|
||||||
|
WGPUTextureFormat surfaceFormat = WGPUTextureFormat_BGRA8Unorm;
|
||||||
|
WGPUQueue queue = nullptr;
|
||||||
|
WGPURenderPipeline worldPipeline = nullptr;
|
||||||
|
WGPURenderPipeline objectPipeline = nullptr;
|
||||||
|
WGPURenderPipeline uiPipeline = nullptr;
|
||||||
|
std::unique_ptr<Util::Texture> depthTexture;
|
||||||
|
|
||||||
|
// Nuklear
|
||||||
|
std::unique_ptr<nk_context> ui;
|
||||||
|
std::unique_ptr<Util::Texture> uiFontTexture;
|
||||||
|
std::unique_ptr<Util::Texture> uiDummyTexture;
|
||||||
|
nk_buffer uiVertexBufferNK;
|
||||||
|
nk_buffer uiIndexBufferNK;
|
||||||
|
nk_buffer uiCommandBufferNK;
|
||||||
|
WGPUBuffer uiVertexBuffer = nullptr;
|
||||||
|
WGPUBuffer uiIndexBuffer = nullptr;
|
||||||
|
WGPUBuffer uiUniformBuffer = nullptr;
|
||||||
|
WGPUSampler uiSampler = nullptr;
|
||||||
|
WGPUBindGroupLayout uiBgl = nullptr;
|
||||||
|
|
||||||
|
// World
|
||||||
|
std::unique_ptr<World::ClientWorld> world;
|
||||||
|
WGPUBuffer worldUniformBuffer = nullptr;
|
||||||
|
WGPUSampler worldSampler = nullptr;
|
||||||
|
WGPUBindGroupLayout worldBgl = nullptr;
|
||||||
|
WGPUBindGroup worldBindGroup = nullptr;
|
||||||
|
std::unique_ptr<Util::Texture> nodeTextureAtlas;
|
||||||
|
std::unique_ptr<ChunkRenderer> chunkRenderer = std::make_unique<ChunkRenderer>(this);
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
std::string assetPath = std::string(getenv("HOME")) + "/eclipse-workspace/ArtifactEngine/assets";
|
||||||
|
glm::mat4 ortho;
|
||||||
|
|
||||||
|
Server* server;
|
||||||
|
|
||||||
|
Engine();
|
||||||
|
~Engine();
|
||||||
|
|
||||||
|
// Initialize the engine: open the window, set up the wgpu device, etc.
|
||||||
|
void init();
|
||||||
|
// Reload shaders, processing overrides if present.
|
||||||
|
void reloadShaders();
|
||||||
|
// Create (and refresh) the pipelines used to render the world.
|
||||||
|
void makeWorldPipelines();
|
||||||
|
// Create (and refresh) the pipeline used to render entities.
|
||||||
|
void makeObjectPipeline();
|
||||||
|
// Create (and refresh) the pipeline used to render the UI.
|
||||||
|
void makeUIPipeline();
|
||||||
|
// Manages the main loop.
|
||||||
|
void run();
|
||||||
|
// Performs rendering per-frame.
|
||||||
|
void render();
|
||||||
|
// Updates game logic per-step.
|
||||||
|
void tick();
|
||||||
|
// Send a message to the server.
|
||||||
|
template<typename T>
|
||||||
|
void sendMessage(T msg);
|
||||||
|
// Receive a message from the server.
|
||||||
|
template<typename T>
|
||||||
|
void receiveMessage(T msg) {
|
||||||
|
if constexpr (std::is_same_v<T, Events::Initialized>) {
|
||||||
|
std::cout << "Message received" << std::endl;
|
||||||
|
} else if constexpr (std::is_same_v<T, Events::ChunkChanged>) {
|
||||||
|
auto ev = (Events::ChunkChanged) msg;
|
||||||
|
world->chunks.at(ev.pos)->data = ev.data;
|
||||||
|
// chunkRenderer->updateChunk(ev.pos, ev.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <Server.h>
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void Artifact::Engine::sendMessage(T msg){
|
||||||
|
if(server) {
|
||||||
|
server->receiveMessage(msg);
|
||||||
|
} else {
|
||||||
|
auto buffer = cista::serialize(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
74
src/core/Events.h
Normal file
74
src/core/Events.h
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
// EventBus.h — header-only, < 100 lines
|
||||||
|
#pragma once
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <typeindex>
|
||||||
|
#include <any>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
namespace Events {
|
||||||
|
// Fired when the engine finishes initializing.
|
||||||
|
struct Initialized {};
|
||||||
|
// Fired every time the engine updates game logic.
|
||||||
|
struct Tick {
|
||||||
|
time_t dtime;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventTarget {
|
||||||
|
public:
|
||||||
|
// Subscribe to an event type
|
||||||
|
template<typename T>
|
||||||
|
using Callback = std::function<void(const T&)>;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void listen(Callback<T> callback) {
|
||||||
|
auto type = std::type_index(typeid(T));
|
||||||
|
auto& vec = callbacks[type];
|
||||||
|
vec.emplace_back([cb = std::move(callback)](const std::any& e) {
|
||||||
|
cb(std::any_cast<const T&>(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire an event — all subscribers get called immediately
|
||||||
|
template<typename T>
|
||||||
|
void dispatch(const T& event) {
|
||||||
|
auto it = callbacks.find(std::type_index(typeid(T)));
|
||||||
|
if (it == callbacks.end()) return;
|
||||||
|
|
||||||
|
// Copy the list in case a callback unsubscribes during dispatch
|
||||||
|
auto copy = it->second;
|
||||||
|
for (const auto& cb : copy)
|
||||||
|
cb(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: queued dispatch (for thread safety or next-frame events)
|
||||||
|
void processQueue() {
|
||||||
|
for (auto& [anyEvent, copy] : queued) {
|
||||||
|
auto it = callbacks.find(anyEvent.type());
|
||||||
|
if (it != callbacks.end()) {
|
||||||
|
for (const auto& cb : copy)
|
||||||
|
cb(anyEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queued.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void queue(const T& event) {
|
||||||
|
auto type = std::type_index(typeid(T));
|
||||||
|
auto it = callbacks.find(type);
|
||||||
|
if (it == callbacks.end()) return;
|
||||||
|
queued.emplace_back(std::any(event), it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using AnyCallback = std::function<void(const std::any&)>;
|
||||||
|
std::unordered_map<std::type_index, std::vector<AnyCallback>> callbacks;
|
||||||
|
std::vector<std::pair<std::any, std::vector<AnyCallback>>> queued;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
46
src/core/Registry.h
Normal file
46
src/core/Registry.h
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Registry {
|
||||||
|
public:
|
||||||
|
using ID = uint32_t; // Or uint16_t if fewer items expected
|
||||||
|
|
||||||
|
ID add(const std::string& name, T&& value) {
|
||||||
|
if (nameToId_.contains(name)) {
|
||||||
|
throw std::runtime_error("Duplicate registration: " + name);
|
||||||
|
}
|
||||||
|
ID id = static_cast<ID>(entries.size());
|
||||||
|
entries.push_back(std::move(value));
|
||||||
|
nameToId_[name] = id;
|
||||||
|
idToName_[id] = name;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const T& get(ID id) const {
|
||||||
|
if (id >= entries.size()) throw std::out_of_range("Invalid ID");
|
||||||
|
return entries[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
ID getID(const std::string& name) const {
|
||||||
|
auto it = nameToId_.find(name);
|
||||||
|
if (it == nameToId_.end()) throw std::runtime_error("Unknown name: " + name);
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& getName(ID id) const {
|
||||||
|
auto it = idToName_.find(id);
|
||||||
|
if (it == idToName_.end()) throw std::out_of_range("Invalid ID");
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Iteration over all entries
|
||||||
|
const std::vector<T>& all() const { return entries; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<T> entries;
|
||||||
|
std::unordered_map<std::string, ID> nameToId_;
|
||||||
|
std::unordered_map<ID, std::string> idToName_; // For reverse lookup
|
||||||
|
};
|
||||||
|
|
||||||
76
src/core/Window.cpp
Normal file
76
src/core/Window.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <glfw3webgpu.h>
|
||||||
|
#include <Window.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
Window::Window() {
|
||||||
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||||
|
window = glfwCreateWindow(1200, 800, "Artifact Engine", nullptr, nullptr);
|
||||||
|
if (!window) throw std::runtime_error("Failed to create GLFW window");
|
||||||
|
glfwSetWindowUserPointer(window, this);
|
||||||
|
|
||||||
|
glfwSetKeyCallback(window, [](GLFWwindow* window, int key, int scancode, int action, int mods){
|
||||||
|
auto me = reinterpret_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
|
me->dispatch(Events::Key{
|
||||||
|
.key = key,
|
||||||
|
.scancode = scancode,
|
||||||
|
.action = action,
|
||||||
|
.mods = mods
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
glfwSetCursorPosCallback(window, [](GLFWwindow* window, double x, double y) {
|
||||||
|
auto me = reinterpret_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
|
me->dispatch(Events::CursorPos{
|
||||||
|
.x = x,
|
||||||
|
.y = y
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
glfwSetMouseButtonCallback(window, [](GLFWwindow* window, int button, int action, int mods) {
|
||||||
|
auto me = reinterpret_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
|
Events::ActionType type;
|
||||||
|
switch(button) {
|
||||||
|
case GLFW_MOUSE_BUTTON_LEFT:
|
||||||
|
type = Events::ActionType::PRIMARY;
|
||||||
|
break;
|
||||||
|
case GLFW_MOUSE_BUTTON_RIGHT:
|
||||||
|
type = Events::ActionType::SECONDARY;
|
||||||
|
break;
|
||||||
|
case GLFW_MOUSE_BUTTON_MIDDLE:
|
||||||
|
type = Events::ActionType::TERTIARY;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
type = Events::ActionType::UNKNOWN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
double x, y;
|
||||||
|
glfwGetCursorPos(window, &x, &y);
|
||||||
|
me->dispatch(Events::Action{
|
||||||
|
.type = type,
|
||||||
|
.state = action == GLFW_PRESS,
|
||||||
|
.x = x,
|
||||||
|
.y = y
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Window::~Window() {
|
||||||
|
glfwDestroyWindow(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
WGPUSurface Window::createWGPUSurface(WGPUInstance &instance) {
|
||||||
|
return glfwCreateWindowWGPUSurface(instance, window);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Window::shouldClose() {
|
||||||
|
return glfwWindowShouldClose(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::close() const {
|
||||||
|
glfwSetWindowShouldClose(window, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
53
src/core/Window.h
Normal file
53
src/core/Window.h
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
#include <webgpu/webgpu.h>
|
||||||
|
|
||||||
|
#include <Events.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
namespace Events {
|
||||||
|
|
||||||
|
struct Key {
|
||||||
|
int key;
|
||||||
|
int scancode;
|
||||||
|
int action;
|
||||||
|
int mods;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CursorPos {
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ActionType {
|
||||||
|
PRIMARY,
|
||||||
|
SECONDARY,
|
||||||
|
TERTIARY,
|
||||||
|
|
||||||
|
UNKNOWN
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Action {
|
||||||
|
ActionType type;
|
||||||
|
bool state;
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Window: public EventTarget {
|
||||||
|
GLFWwindow* window;
|
||||||
|
public:
|
||||||
|
int width = 1200;
|
||||||
|
int height = 800;
|
||||||
|
Window();
|
||||||
|
~Window();
|
||||||
|
WGPUSurface createWGPUSurface(WGPUInstance &instance);
|
||||||
|
bool shouldClose();
|
||||||
|
void close() const;
|
||||||
|
// TODO: Allow changing the window cursor.
|
||||||
|
void setCursor();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
52
src/main.cpp
Normal file
52
src/main.cpp
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <Events.h>
|
||||||
|
#include <Engine.h>
|
||||||
|
#include <World.h>
|
||||||
|
#include <Server.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// printf("About to call glfwInit...\n");
|
||||||
|
// fflush(stdout);
|
||||||
|
// if (!glfwInit()) {
|
||||||
|
// fprintf(stderr, "glfwInit failed\n");
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
// printf("glfwInit succeeded!\n");
|
||||||
|
//
|
||||||
|
// std::cout << "Starting..." << std::endl;
|
||||||
|
|
||||||
|
// glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||||
|
// auto window = glfwCreateWindow(1200, 800, "Test", nullptr, nullptr);
|
||||||
|
//
|
||||||
|
// std::cout << "GLFW window created" << std::endl;
|
||||||
|
|
||||||
|
Artifact::Engine engine;
|
||||||
|
|
||||||
|
Artifact::Server server(&engine);
|
||||||
|
engine.server = &server;
|
||||||
|
|
||||||
|
engine.listen<Artifact::Events::Initialized>([](const Artifact::Events::Initialized &ev) {
|
||||||
|
std::cout << "Initialization complete..." << std::endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.listen<Artifact::Events::DrawUI>([&engine](Artifact::Events::DrawUI ev) {
|
||||||
|
if(nk_begin(ev.ctx, "Test", nk_rect(0, 0, 100, 100), NK_WINDOW_MOVABLE|NK_WINDOW_SCALABLE|NK_WINDOW_TITLE|NK_WINDOW_BORDER)) {
|
||||||
|
nk_layout_row_dynamic(ev.ctx, 50, 2);
|
||||||
|
nk_label(ev.ctx, "Test", NK_TEXT_ALIGN_CENTERED);
|
||||||
|
if(nk_button_label(ev.ctx, "Test")) {
|
||||||
|
engine.server->sendMessage(Artifact::Events::Initialized{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nk_end(ev.ctx);
|
||||||
|
});
|
||||||
|
|
||||||
|
Artifact::World::NodeDef def_Stone;
|
||||||
|
def_Stone.texture = "test.png";
|
||||||
|
engine.world->nodeRegistry.registerNode("test", def_Stone);
|
||||||
|
|
||||||
|
|
||||||
|
engine.run();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
35
src/network/Network.h
Normal file
35
src/network/Network.h
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <glm.hpp>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
namespace Network {
|
||||||
|
|
||||||
|
enum PacketType {
|
||||||
|
UPDATE_PLAYER_POS = 0,
|
||||||
|
UPDATE_PLAYER_ROTATION,
|
||||||
|
CHAT_MESSAGE,
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace Packet {
|
||||||
|
|
||||||
|
struct PlayerPos {
|
||||||
|
std::string name;
|
||||||
|
glm::vec3 pos;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PlayerRotation {
|
||||||
|
std::string name;
|
||||||
|
glm::quat4 rot;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ChatMessage {
|
||||||
|
std::string name;
|
||||||
|
std::string msg;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/player/Player.h
Normal file
12
src/player/Player.h
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#include <glm.hpp>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
namespace Player {
|
||||||
|
|
||||||
|
class Player {
|
||||||
|
glm::vec3 pos;
|
||||||
|
double fov = 72.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/server/Server.cpp
Normal file
66
src/server/Server.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
#include <ngtcp2/ngtcp2_crypto.h>
|
||||||
|
#include <gnutls/gnutls.h>
|
||||||
|
|
||||||
|
#include <Engine.h>
|
||||||
|
#include <Server.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
Server::Server(Engine* localClient) : localClient(localClient) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::run() {
|
||||||
|
start();
|
||||||
|
time_t lastTime;
|
||||||
|
time(&lastTime);
|
||||||
|
while(true) {
|
||||||
|
time_t now;
|
||||||
|
time(&now);
|
||||||
|
if(now - lastTime > STEP_SIZE) {
|
||||||
|
tick();
|
||||||
|
dispatch(Events::Tick{now - lastTime});
|
||||||
|
lastTime = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void log_printf(void *user_data, const char *fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vfprintf(stderr, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
fputc('\n', stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::start() {
|
||||||
|
// ngtcp2_settings settings;
|
||||||
|
// ngtcp2_settings_default(&settings);
|
||||||
|
// settings.log_printf = log_printf;
|
||||||
|
// settings.initial_ts = get_time_ns();
|
||||||
|
//
|
||||||
|
// ngtcp2_transport_params params;
|
||||||
|
// ngtcp2_transport_params_default(¶ms);
|
||||||
|
//
|
||||||
|
// ngtcp2_callbacks callbacks = {0};
|
||||||
|
// // Set required callbacks using ngtcp2_crypto_* helpers
|
||||||
|
// callbacks.client_initial = ngtcp2_crypto_client_initial_cb;
|
||||||
|
// callbacks.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb;
|
||||||
|
// callbacks.encrypt = ngtcp2_crypto_encrypt_cb;
|
||||||
|
// callbacks.decrypt = ngtcp2_crypto_decrypt_cb;
|
||||||
|
// callbacks.hp_mask = ngtcp2_crypto_hp_mask_cb;
|
||||||
|
// callbacks.recv_retry = ngtcp2_crypto_recv_retry_cb;
|
||||||
|
// callbacks.update_key = ngtcp2_crypto_update_key_cb;
|
||||||
|
// callbacks.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb;
|
||||||
|
// callbacks.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb;
|
||||||
|
// callbacks.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb;
|
||||||
|
// callbacks.version_negotiation = ngtcp2_crypto_version_negotiation_cb;
|
||||||
|
//
|
||||||
|
// ngtcp2_path path = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::tick() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
45
src/server/Server.h
Normal file
45
src/server/Server.h
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#pragma once
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <ngtcp2/ngtcp2.h>
|
||||||
|
#include <cista.h>
|
||||||
|
|
||||||
|
#include <Events.h>
|
||||||
|
|
||||||
|
#define STEP_SIZE 200
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
class Engine;
|
||||||
|
|
||||||
|
class Server: public EventTarget {
|
||||||
|
Artifact::Engine* localClient;
|
||||||
|
public:
|
||||||
|
Server(Engine* localClient);
|
||||||
|
|
||||||
|
void run();
|
||||||
|
void start();
|
||||||
|
void tick();
|
||||||
|
template<typename T>
|
||||||
|
void sendMessage(T msg);
|
||||||
|
// Process a message from the client.
|
||||||
|
template<typename T>
|
||||||
|
void receiveMessage(T msg) {
|
||||||
|
if constexpr (std::is_same_v<T, Events::Initialized>) {
|
||||||
|
std::cout << "Message received" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <Engine.h>
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void Artifact::Server::sendMessage(T msg) {
|
||||||
|
if(localClient) {
|
||||||
|
localClient->receiveMessage(msg);
|
||||||
|
} else {
|
||||||
|
auto buffer = cista::serialize(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/server/ServerClient.cpp
Normal file
9
src/server/ServerClient.cpp
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#include <ServerClient.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
ServerClient::ServerClient(ngtcp2_conn* connection, std::string name) : connection(connection, &ngtcp2_conn_del), name(name) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
18
src/server/ServerClient.h
Normal file
18
src/server/ServerClient.h
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <ngtcp2/ngtcp2.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
class ServerClient {
|
||||||
|
std::unique_ptr<ngtcp2_conn, decltype(&ngtcp2_conn_del)> connection;
|
||||||
|
|
||||||
|
ServerClient();
|
||||||
|
public:
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
ServerClient(ngtcp2_conn* connection, std::string name);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
45
src/server/ServerWorld.cpp
Normal file
45
src/server/ServerWorld.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
|
||||||
|
#include <ServerWorld.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
namespace World {
|
||||||
|
|
||||||
|
ServerWorld::ServerWorld(Server* server) : server(server) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerWorld::tick() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerWorld::updateLoadedChunks() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Chunk* ServerWorld::loadChunk(glm::ivec3 pos) {
|
||||||
|
auto chunk = chunks.at(pos).get();
|
||||||
|
if(chunk) {
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Load from disk
|
||||||
|
|
||||||
|
std::array<uint16_t, Chunk::SIZE_CUBED> data;
|
||||||
|
for(int x = 0; x < CHUNK_SIZE; ++x)
|
||||||
|
for(int z = 0; z < CHUNK_SIZE; ++z) {
|
||||||
|
int height = 8 + ((pos.x * 7 + pos.z * 13 + x * 3 + z * 5) % 8); // Heights 8 to 15
|
||||||
|
if (height < 0) height = 0;
|
||||||
|
if (height >= Chunk::SIZE) height = Chunk::SIZE - 1;
|
||||||
|
for (int y = 0; y <= 7; ++y) {
|
||||||
|
data[x + y * Chunk::SIZE + z * Chunk::SIZE * Chunk::SIZE] = std::rand() % 3 + 1; // Stone
|
||||||
|
}
|
||||||
|
if (x > 3 && x < Chunk::SIZE - 3 && z > 3 && z < Chunk::SIZE - 3) {
|
||||||
|
data[x + 8 * Chunk::SIZE + z * Chunk::SIZE * Chunk::SIZE] = 2; // Grass on top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chunks.at(pos) = std::make_unique<Chunk>(data);
|
||||||
|
return chunks.at(pos).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/server/ServerWorld.h
Normal file
20
src/server/ServerWorld.h
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <World.h>
|
||||||
|
#include <Server.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
namespace World {
|
||||||
|
|
||||||
|
class ServerWorld: public World {
|
||||||
|
Server* server = nullptr;
|
||||||
|
public:
|
||||||
|
ServerWorld(Server* server);
|
||||||
|
|
||||||
|
void tick();
|
||||||
|
void updateLoadedChunks();
|
||||||
|
Chunk* loadChunk(glm::ivec3 pos);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/util/Mesh.h
Normal file
18
src/util/Mesh.h
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glm.hpp>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
float pos[3];
|
||||||
|
float uv[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Mesh {
|
||||||
|
std::unique_ptr<std::vector<Vertex>> vertices;
|
||||||
|
std::unique_ptr<std::vector<int>> indices;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
65
src/util/Paths.cpp
Normal file
65
src/util/Paths.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
#include <Paths.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
std::string getExecutablePath() {
|
||||||
|
std::string path;
|
||||||
|
char buffer[PATH_MAX]; // PATH_MAX is typically 4096 on Unix-like systems
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Windows: Use GetModuleFileName
|
||||||
|
DWORD size = GetModuleFileNameA(NULL, buffer, PATH_MAX);
|
||||||
|
if (size == 0 || size == PATH_MAX) {
|
||||||
|
throw std::runtime_error("Failed to get executable path on Windows");
|
||||||
|
}
|
||||||
|
path = std::string(buffer, size);
|
||||||
|
// Remove the executable name by finding the last backslash
|
||||||
|
size_t lastSlash = path.find_last_of("\\");
|
||||||
|
if (lastSlash != std::string::npos) {
|
||||||
|
path = path.substr(0, lastSlash + 1); // Include the trailing slash
|
||||||
|
} else {
|
||||||
|
path = ""; // Fallback to current directory
|
||||||
|
}
|
||||||
|
#elif __APPLE__
|
||||||
|
// macOS: Use _NSGetExecutablePath
|
||||||
|
uint32_t size = sizeof(buffer);
|
||||||
|
if (_NSGetExecutablePath(buffer, &size) != 0) {
|
||||||
|
throw std::runtime_error("Failed to get executable path on macOS");
|
||||||
|
}
|
||||||
|
path = std::string(buffer);
|
||||||
|
// Remove the executable name by finding the last slash
|
||||||
|
size_t lastSlash = path.find_last_of("/");
|
||||||
|
if (lastSlash != std::string::npos) {
|
||||||
|
path = path.substr(0, lastSlash + 1); // Include the trailing slash
|
||||||
|
} else {
|
||||||
|
path = ""; // Fallback to current directory
|
||||||
|
}
|
||||||
|
#elif WASM_BUILD
|
||||||
|
path = "";
|
||||||
|
#else
|
||||||
|
// Linux: Use /proc/self/exe
|
||||||
|
ssize_t count = readlink("/proc/self/exe", buffer, PATH_MAX);
|
||||||
|
if (count == -1) {
|
||||||
|
throw std::runtime_error("Failed to get executable path on Linux");
|
||||||
|
}
|
||||||
|
path = std::string(buffer, count);
|
||||||
|
// Remove the executable name by finding the last slash
|
||||||
|
size_t lastSlash = path.find_last_of("/");
|
||||||
|
if (lastSlash != std::string::npos) {
|
||||||
|
path = path.substr(0, lastSlash + 1); // Include the trailing slash
|
||||||
|
} else {
|
||||||
|
path = ""; // Fallback to current directory
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string globalAssetPath = getExecutablePath() + "../assets";
|
||||||
|
|
||||||
|
std::string getResourcePath(std::string name) {
|
||||||
|
return globalAssetPath + "/" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
14
src/util/Paths.h
Normal file
14
src/util/Paths.h
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <mach-o/dyld.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
std::string getExecutablePath();
|
||||||
|
|
||||||
|
std::string getResourcePath(std::string name);
|
||||||
|
|
||||||
|
}
|
||||||
53
src/util/Texture.cpp
Normal file
53
src/util/Texture.cpp
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include <Texture.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
namespace Util {
|
||||||
|
|
||||||
|
std::unique_ptr<Texture> createTextureFromData(WGPUDevice device, WGPUQueue queue, const void* data, uint32_t width, uint32_t height) {
|
||||||
|
auto tex = std::make_unique<Texture>();
|
||||||
|
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;
|
||||||
|
tex->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 = tex->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 tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Texture> createTextureFromFile(WGPUDevice device, WGPUQueue queue, const char* file) {
|
||||||
|
int width, height, channels;
|
||||||
|
auto data = stbi_load(file, &width, &height, &channels, 4);
|
||||||
|
auto out = createTextureFromData(device, queue, data, width, height);
|
||||||
|
stbi_image_free(data);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/util/Texture.h
Normal file
18
src/util/Texture.h
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
#include <webgpu/webgpu.h>
|
||||||
|
#include <stb_image.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
namespace Util {
|
||||||
|
|
||||||
|
struct Texture {
|
||||||
|
WGPUTexture texture;
|
||||||
|
WGPUTextureView view;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<Texture> createTextureFromData(WGPUDevice device, WGPUQueue queue, const void* data, uint32_t width, uint32_t height);
|
||||||
|
|
||||||
|
std::unique_ptr<Texture> createTextureFromFile(WGPUDevice device, WGPUQueue queue, const char* file);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/util/UI.h
Normal file
21
src/util/UI.h
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
#include <glm.hpp>
|
||||||
|
#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
|
||||||
|
#define NK_IMPLEMENTATION
|
||||||
|
#include <nuklear.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
|
||||||
|
struct NKVertex {
|
||||||
|
float pos[2];
|
||||||
|
float uv[2];
|
||||||
|
uint8_t color[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
36
src/util/Util.h
Normal file
36
src/util/Util.h
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <glm.hpp>
|
||||||
|
#include <webgpu/webgpu.h>
|
||||||
|
#include <stb_image.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
namespace Util {
|
||||||
|
|
||||||
|
// Custom hash for glm::vec3
|
||||||
|
struct Vec3Hash {
|
||||||
|
std::size_t operator()(const glm::vec3& v) const noexcept {
|
||||||
|
// Combine x, y, z into a single hash
|
||||||
|
// Using std::hash<float> and a good mixing function
|
||||||
|
std::size_t h1 = std::hash<float>{}(v.x);
|
||||||
|
std::size_t h2 = std::hash<float>{}(v.y);
|
||||||
|
std::size_t h3 = std::hash<float>{}(v.z);
|
||||||
|
|
||||||
|
return h1 ^ (h2 << 1) ^ (h3 << 2); // Simple but effective mixing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Custom equality for glm::vec3
|
||||||
|
struct Vec3Equal {
|
||||||
|
bool operator()(const glm::vec3& lhs, const glm::vec3& rhs) const noexcept {
|
||||||
|
// Use exact equality or epsilon comparison
|
||||||
|
constexpr float EPSILON = 1e-6f;
|
||||||
|
return std::abs(lhs.x - rhs.x) < EPSILON &&
|
||||||
|
std::abs(lhs.y - rhs.y) < EPSILON &&
|
||||||
|
std::abs(lhs.z - rhs.z) < EPSILON;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/world/Chunk.cpp
Normal file
23
src/world/Chunk.cpp
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <World.h>
|
||||||
|
#include <Chunk.h>
|
||||||
|
#include <Mesh.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
namespace World {
|
||||||
|
|
||||||
|
Chunk::Chunk() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Chunk::~Chunk() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Chunk::getNodeRaw(float x, float y, float z) {
|
||||||
|
return data.at(posToIndex(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/world/Chunk.h
Normal file
38
src/world/Chunk.h
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifndef CHUNK_SIZE
|
||||||
|
# define CHUNK_SIZE 16
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
namespace World {
|
||||||
|
|
||||||
|
int posToIndex(float x, float y, float z);
|
||||||
|
|
||||||
|
class Chunk {
|
||||||
|
public:
|
||||||
|
static constexpr int SIZE = CHUNK_SIZE;
|
||||||
|
static constexpr int SIZE_CUBED = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
|
||||||
|
|
||||||
|
bool dirty = false;
|
||||||
|
std::array<uint16_t, SIZE_CUBED> data;
|
||||||
|
|
||||||
|
Chunk();
|
||||||
|
Chunk(std::array<uint16_t, SIZE_CUBED> data) : data(data) {};
|
||||||
|
~Chunk();
|
||||||
|
|
||||||
|
uint16_t getNodeRaw(float x, float y, float z);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Events {
|
||||||
|
|
||||||
|
struct ChunkChanged {
|
||||||
|
glm::ivec3 pos;
|
||||||
|
std::array<uint16_t, World::Chunk::SIZE_CUBED> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
83
src/world/World.cpp
Normal file
83
src/world/World.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <stb_image.h>
|
||||||
|
|
||||||
|
#include <World.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
namespace World {
|
||||||
|
|
||||||
|
int posToIndex(float x, float y, float z) {
|
||||||
|
return x + y * CHUNK_SIZE + z * CHUNK_SIZE * CHUNK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeRegistry::NodeRegistry() {
|
||||||
|
registerNode("air", NodeDef{});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NodeRegistry::generateTextureAtlas(int& atlasWidth, int& atlasHeight, std::vector<unsigned char>& atlasData) {
|
||||||
|
if (registeredNodes.size() <= 1) { // Only air (ID 0) exists
|
||||||
|
std::cerr << "No textures to create atlas\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint16_t> drawables;
|
||||||
|
|
||||||
|
// Load all textures and find the maximum dimensions
|
||||||
|
std::vector<std::vector<unsigned char>> textureData;
|
||||||
|
std::vector<int> widths, heights;
|
||||||
|
int maxWidth = 0, maxHeight = 0;
|
||||||
|
for (size_t i = 1; i < registeredNodes.size(); ++i) { // Skip ID 0 (air)
|
||||||
|
if(registeredNodes.at(i).visual == NodeVisual::NONE) continue;
|
||||||
|
drawables.push_back(i);
|
||||||
|
int width, height, channels;
|
||||||
|
unsigned char* data = stbi_load(registeredNodes.at(i).texture.c_str(), &width, &height, &channels, 4);
|
||||||
|
if (!data) {
|
||||||
|
std::cerr << "Failed to load texture: " << registeredNodes.at(i).texture << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
textureData.push_back({});
|
||||||
|
textureData.at(i).resize(width * height * 4);
|
||||||
|
std::memcpy(textureData[i].data(), data, width * height * 4);
|
||||||
|
widths.push_back(width);
|
||||||
|
heights.push_back(height);
|
||||||
|
maxWidth = std::max(maxWidth, width);
|
||||||
|
maxHeight = std::max(maxHeight, height);
|
||||||
|
stbi_image_free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple atlas layout: stack textures horizontally
|
||||||
|
// (You can improve this with a more efficient packing algorithm if needed)
|
||||||
|
atlasWidth = maxWidth * (drawables.size()) + 1; // -1 because ID 0 (air) has no texture
|
||||||
|
atlasHeight = maxHeight + 1;
|
||||||
|
atlasData.resize(atlasWidth * atlasHeight * 4, 0); // RGBA
|
||||||
|
|
||||||
|
for (size_t i = 1; i < drawables.size(); ++i) {
|
||||||
|
int xOffset = (i - 1) * maxWidth;
|
||||||
|
for (int y = 0; y < heights[i]; ++y) {
|
||||||
|
for (int x = 0; x < widths[i]; ++x) {
|
||||||
|
int srcIdx = (y * widths[i] + x) * 4;
|
||||||
|
int dstIdx = (y * atlasWidth + (xOffset + x)) * 4;
|
||||||
|
for (int c = 0; c < 4; ++c) {
|
||||||
|
atlasData[dstIdx + c] = textureData[i][srcIdx + c];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update texture coordinates (normalized 0-1)
|
||||||
|
float uMin = static_cast<float>(xOffset) / atlasWidth;
|
||||||
|
float uMax = static_cast<float>(xOffset + widths[i]) / atlasWidth;
|
||||||
|
float vMin = 0.0f;
|
||||||
|
float vMax = static_cast<float>(heights[i]) / atlasHeight;
|
||||||
|
textureCoords.at(drawables.at(i)) = {{uMin, vMin}, {uMax, vMax}};
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
World::World() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/world/World.h
Normal file
66
src/world/World.h
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
#pragma once
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include <glm.hpp>
|
||||||
|
|
||||||
|
#include <Chunk.h>
|
||||||
|
#include <Util.h>
|
||||||
|
#include <Paths.h>
|
||||||
|
#include <Mesh.h>
|
||||||
|
#include <Texture.h>
|
||||||
|
|
||||||
|
namespace Artifact {
|
||||||
|
namespace World {
|
||||||
|
|
||||||
|
enum class AlphaBlendMode {
|
||||||
|
NONE,
|
||||||
|
CLIP,
|
||||||
|
BLEND
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class NodeVisual {
|
||||||
|
NONE,
|
||||||
|
CUBE,
|
||||||
|
MESH
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NodeDef {
|
||||||
|
std::string name;
|
||||||
|
std::string texture;
|
||||||
|
AlphaBlendMode alphaBlendMode = AlphaBlendMode::NONE;
|
||||||
|
NodeVisual visual = NodeVisual::NONE;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NodeTextureCoords {
|
||||||
|
glm::vec2 uvMin;
|
||||||
|
glm::vec2 uvMax;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class NodeRegistry {
|
||||||
|
uint16_t next = 0;
|
||||||
|
public:
|
||||||
|
std::unordered_map<uint16_t, NodeDef> registeredNodes;
|
||||||
|
std::vector<NodeTextureCoords> textureCoords;
|
||||||
|
|
||||||
|
NodeRegistry();
|
||||||
|
|
||||||
|
void registerNode(std::string name, NodeDef def) {
|
||||||
|
def.name = name;
|
||||||
|
def.texture = getResourcePath(def.texture);
|
||||||
|
registeredNodes.emplace(next++, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool generateTextureAtlas(int& atlasWidth, int& atlasHeight, std::vector<unsigned char>& atlasData);
|
||||||
|
};
|
||||||
|
|
||||||
|
class World {
|
||||||
|
public:
|
||||||
|
NodeRegistry nodeRegistry;
|
||||||
|
std::unordered_map<glm::ivec3, std::unique_ptr<Chunk>, Artifact::Util::Vec3Hash, Artifact::Util::Vec3Equal> chunks;
|
||||||
|
|
||||||
|
World();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
120
tools/update_deps.sh
Normal file
120
tools/update_deps.sh
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CMAKE="$HOME/Downloads/cmake-3.28.3-macos-universal/CMake.app/Contents/bin/cmake"
|
||||||
|
|
||||||
|
DEPS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../deps" && pwd)"
|
||||||
|
mkdir -p "$DEPS_DIR"
|
||||||
|
|
||||||
|
cd $DEPS_DIR
|
||||||
|
|
||||||
|
# GLFW
|
||||||
|
if [ ! -d "glfw/build" ] || [ ! -f "glfw/build/src/libglfw3.a" ]; then
|
||||||
|
echo "Building GLFW..."
|
||||||
|
|
||||||
|
if [ ! -d "glfw" ]; then
|
||||||
|
echo " Cloning GLFW 3.4..."
|
||||||
|
git clone --depth 1 --branch 3.4 https://github.com/glfw/glfw.git glfw
|
||||||
|
else
|
||||||
|
echo " Updating GLFW..."
|
||||||
|
git -C glfw pull
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd glfw
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
|
||||||
|
"$CMAKE" .. \
|
||||||
|
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||||
|
-DBUILD_SHARED_LIBS=OFF \
|
||||||
|
-DGLFW_BUILD_EXAMPLES=OFF \
|
||||||
|
-DGLFW_BUILD_TESTS=OFF \
|
||||||
|
-DGLFW_BUILD_DOCS=OFF \
|
||||||
|
-DGLFW_INSTALL=ON \
|
||||||
|
-DGLFW_BUILD_COCOA=ON \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="$DEPS_DIR/glfw/install"
|
||||||
|
|
||||||
|
make -j$(sysctl -n hw.logicalcpu)
|
||||||
|
make install # installs libglfw3.a + headers into $DEPS_DIR/glfw/install
|
||||||
|
|
||||||
|
cd ../..
|
||||||
|
echo "GLFW built and installed to $DEPS_DIR/glfw/install"
|
||||||
|
else
|
||||||
|
echo "GLFW already built – skipping."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If you don't use -L, using curl to access 'modern' websites can get very annoying.
|
||||||
|
if [ ! -d "$DEPS_DIR/webgpu" ]; then
|
||||||
|
mkdir -p "$DEPS_DIR/webgpu"
|
||||||
|
curl -L https://github.com/gfx-rs/wgpu-native/releases/download/v27.0.2.0/wgpu-macos-aarch64-debug.zip -o "$DEPS_DIR/webgpu/wgpu-macos-aarch64-debug.zip"
|
||||||
|
cd webgpu
|
||||||
|
unzip wgpu-macos-aarch64-debug.zip
|
||||||
|
rm wgpu-macos-aarch64-debug.zip
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
|
|
||||||
|
# glfw3webgpu helper
|
||||||
|
if [ ! -d "$DEPS_DIR/glfw3webgpu" ]; then
|
||||||
|
git clone --depth 1 https://github.com/eliemichel/glfw3webgpu.git "$DEPS_DIR/glfw3webgpu"
|
||||||
|
else
|
||||||
|
git -C "$DEPS_DIR/glfw3webgpu" pull
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Nuklear (single header)
|
||||||
|
curl -L https://raw.githubusercontent.com/Immediate-Mode-UI/Nuklear/master/nuklear.h -o "$DEPS_DIR/nuklear.h"
|
||||||
|
|
||||||
|
if [ ! -d "glm" ]; then
|
||||||
|
git clone --depth 1 https://github.com/g-truc/glm.git glm
|
||||||
|
else
|
||||||
|
git -C glm fetch origin
|
||||||
|
git -C glm reset --hard origin/master
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cista (serialization)
|
||||||
|
curl -L https://github.com/felixguendling/cista/releases/download/v0.16/cista.h -o "$DEPS_DIR/cista.h"
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# ngtcp2 + ngtcp2_crypto_openssl (static libraries)
|
||||||
|
# ================================================================
|
||||||
|
if [ ! -d "ngtcp2/build" ] || [ ! -f "ngtcp2/build/libngtcp2.a" ]; then
|
||||||
|
echo "Building ngtcp2 (with OpenSSL crypto backend)..."
|
||||||
|
|
||||||
|
if [ ! -d "ngtcp2" ]; then
|
||||||
|
echo " Cloning ngtcp2..."
|
||||||
|
git clone --depth 1 --branch v1.9.0 https://github.com/ngtcp2/ngtcp2.git ngtcp2
|
||||||
|
else
|
||||||
|
echo " Updating ngtcp2..."
|
||||||
|
git -C ngtcp2 pull
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ngtcp2
|
||||||
|
|
||||||
|
# Ensure we have the submodules (crypto/openssl)
|
||||||
|
git submodule update --init --depth 1
|
||||||
|
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
|
||||||
|
# Static-only build, disable examples/tests, enable gnutls backend
|
||||||
|
$CMAKE .. \
|
||||||
|
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||||
|
-DBUILD_SHARED_LIBS=OFF \
|
||||||
|
-DBUILD_STATIC_LIBS=ON \
|
||||||
|
-DENABLE_EXAMPLES=OFF \
|
||||||
|
-DENABLE_TEST=OFF \
|
||||||
|
-DENABLE_OPENSSL=OFF \
|
||||||
|
-DENABLE_GNUTLS=ON \
|
||||||
|
-DGNUTLS_LIBRARY=$HOME/homebrew/Cellar/gnutls/3.8.11/lib/libgnutls.dylib \
|
||||||
|
-DGNUTLS_INCLUDE_DIR=$HOME/homebrew/Cellar/gnutls/3.8.11/include \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="$DEPS_DIR/ngtcp2/install"
|
||||||
|
|
||||||
|
make -j$(sysctl -n hw.logicalcpu)
|
||||||
|
make install # installs headers + .a files into $DEPS_DIR/ngtcp2/install
|
||||||
|
|
||||||
|
cd ../../
|
||||||
|
echo "ngtcp2 built and installed to $DEPS_DIR/ngtcp2/install"
|
||||||
|
else
|
||||||
|
echo "ngtcp2 already built – skipping."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "All dependencies updated."
|
||||||
Loading…
Add table
Add a link
Reference in a new issue