#define WEBGPU_CPP_IMPLEMENTATION #include #include #include #include #include #include "rwgpu.hpp" void onWindowResize(GLFWwindow* window, int width, int height) { auto that = reinterpret_cast(glfwGetWindowUserPointer(window)); // Resize that so that this can work if (that != nullptr) that->resize(width, height); } namespace hibis::rwgpu { RWGPU::RWGPU(std::string title, IntVec2 size, LoggerCallback callback) : Renderer(callback) { wgpu::InstanceDescriptor instanceDescriptor = {}; wgpu::RequestAdapterOptions reqAdaptOpts = {}; wgpu::DeviceDescriptor deviceDescriptor = {}; deviceDescriptor.nextInChain = nullptr; deviceDescriptor.label = "My Device"; // anything works here, that's your call deviceDescriptor.requiredFeaturesCount = 0; // we do not require any specific feature deviceDescriptor.requiredLimits = nullptr; // we do not require any specific limit deviceDescriptor.defaultQueue.nextInChain = nullptr; deviceDescriptor.defaultQueue.label = "The default queue"; mWebGPUInstance = wgpu::createInstance(instanceDescriptor); mWebGPUAdapter = requestAdapter(&reqAdaptOpts); mWebGPUDevice = requestDevice(&deviceDescriptor); mWebGPUQueue = mWebGPUDevice.getQueue(); if (!mWebGPUInstance) { mLogger(Fatal, "Could not initialize WebGPU!"); exit(1); } else { const void * address = static_cast(mWebGPUInstance); std::stringstream pointerAsStream; pointerAsStream << address; mLogger(Information, fmt::format("WGPU Instance Addr: {}", pointerAsStream.str())); } glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); mWindow = glfwCreateWindow(size.x, size.y, title.c_str(), NULL, NULL); glfwSetWindowUserPointer(mWindow, this); glfwSetFramebufferSizeCallback(mWindow, onWindowResize); mWebGPUSurface = glfwGetWGPUSurface(mWebGPUInstance, mWindow); size_t featureCount = wgpuAdapterEnumerateFeatures(mWebGPUAdapter, nullptr); // Allocate memory (could be a new, or a malloc() if this were a C program) mWebGPUFeatures.resize(featureCount); // Call the function a second time, with a non-null return address wgpuAdapterEnumerateFeatures(mWebGPUAdapter, mWebGPUFeatures.data()); mLogger(Information, "Adapter features: "); for (wgpu::FeatureName f : mWebGPUFeatures) { mLogger(Information, fmt::format("WebGPU Feature: {}", f)); } auto onDeviceError = [](WGPUErrorType type, char const* message, void* /* pUserData */) { std::cout << "Uncaptured device error: type " << type; if (message) std::cout << " (" << message << ")"; std::cout << std::endl; }; wgpuDeviceSetUncapturedErrorCallback(mWebGPUDevice, onDeviceError, nullptr /* pUserData */); // Not implemented, causes a panic // auto onQueueWorkDone = [](WGPUQueueWorkDoneStatus status, void* /* pUserData */) { // std::cout << "Queued work finished with status: " << status << std::endl; // }; // wgpuQueueOnSubmittedWorkDone(mWebGPUQueue, onQueueWorkDone, nullptr /* pUserData */); setupSwapChain(size.x, size.y); } RWGPU::~RWGPU() { glfwDestroyWindow(mWindow); mWebGPUSwapChain.drop(); mWebGPUSurface.drop(); mWebGPUDevice.drop(); mWebGPUAdapter.drop(); mWebGPUInstance.drop(); } WGPUAdapter RWGPU::requestAdapter(WGPURequestAdapterOptions const * options) { // A simple structure holding the local information shared with the // onAdapterRequestEnded callback. struct UserData { WGPUAdapter adapter = nullptr; bool requestEnded = false; }; UserData userData; // Callback called by wgpuInstanceRequestAdapter when the request returns // This is a C++ lambda function, but could be any function defined in the // global scope. It must be non-capturing (the brackets [] are empty) so // that it behaves like a regular C function pointer, which is what // wgpuInstanceRequestAdapter expects (WebGPU being a C API). The workaround // is to convey what we want to capture through the pUserData pointer, // provided as the last argument of wgpuInstanceRequestAdapter and received // by the callback as its last argument. auto onAdapterRequestEnded = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, char const * message, void * pUserData) { UserData& userData = *reinterpret_cast(pUserData); if (status == WGPURequestAdapterStatus_Success) { userData.adapter = adapter; } else { std::cout << "Could not get WebGPU adapter: " << message << std::endl; } userData.requestEnded = true; }; // Call to the WebGPU request adapter procedure wgpuInstanceRequestAdapter( mWebGPUInstance /* equivalent of navigator.gpu */, options, onAdapterRequestEnded, (void*)&userData ); // In theory we should wait until onAdapterReady has been called, which // could take some time (what the 'await' keyword does in the JavaScript // code). In practice, we know that when the wgpuInstanceRequestAdapter() // function returns its callback has been called. assert(userData.requestEnded); return userData.adapter; } WGPUDevice RWGPU::requestDevice(WGPUDeviceDescriptor const * descriptor) { struct UserData { WGPUDevice device = nullptr; bool requestEnded = false; }; UserData userData; auto onDeviceRequestEnded = [](WGPURequestDeviceStatus status, WGPUDevice device, char const * message, void * pUserData) { UserData& userData = *reinterpret_cast(pUserData); if (status == WGPURequestDeviceStatus_Success) { userData.device = device; } else { std::cout << "Could not get WebGPU device: " << message << std::endl; } userData.requestEnded = true; }; wgpuAdapterRequestDevice( mWebGPUAdapter, descriptor, onDeviceRequestEnded, (void*)&userData ); assert(userData.requestEnded); return userData.device; } void RWGPU::setupSwapChain(unsigned int width, unsigned int height) { // Drop swap chain if it currently exists if (mWebGPUSwapChain) mWebGPUSwapChain.drop(); // Remake swap chain WGPUSwapChainDescriptor swapChainDesc = {}; swapChainDesc.nextInChain = nullptr; swapChainDesc.width = width; swapChainDesc.height = height; WGPUTextureFormat swapChainFormat = wgpuSurfaceGetPreferredFormat(mWebGPUSurface, mWebGPUAdapter); swapChainDesc.format = swapChainFormat; swapChainDesc.usage = WGPUTextureUsage_RenderAttachment; swapChainDesc.presentMode = WGPUPresentMode_Fifo; mWebGPUSwapChain = wgpuDeviceCreateSwapChain(mWebGPUDevice, mWebGPUSurface, &swapChainDesc); } void RWGPU::clearScreen(Color col) { mCurrentClearColor = {col.r, col.g, col.b, col.a}; } void RWGPU::renderCurrent() { wgpu::TextureView nextTexture = mWebGPUSwapChain.getCurrentTextureView(); if (!nextTexture) { mLogger(Error, "Couldn't create next texture"); return; } WGPUCommandEncoderDescriptor encoderDesc = {}; encoderDesc.nextInChain = nullptr; encoderDesc.label = "HibisDrawCommandEncoder"; WGPUCommandEncoder encoder = mWebGPUDevice.createCommandEncoder(encoderDesc); TODO("Implement Deferred Renderer") WGPURenderPassDescriptor renderPassDesc = {}; WGPURenderPassColorAttachment renderPassColorAttachment = {}; renderPassDesc.colorAttachmentCount = 1; renderPassDesc.colorAttachments = &renderPassColorAttachment; renderPassColorAttachment.view = nextTexture; renderPassColorAttachment.resolveTarget = nullptr; renderPassColorAttachment.loadOp = WGPULoadOp_Clear; renderPassColorAttachment.storeOp = WGPUStoreOp_Store; renderPassColorAttachment.clearValue = mCurrentClearColor; renderPassDesc.depthStencilAttachment = nullptr; renderPassDesc.timestampWriteCount = 0; renderPassDesc.timestampWrites = nullptr; renderPassDesc.nextInChain = nullptr; wgpu::RenderPassEncoder renderPass = wgpuCommandEncoderBeginRenderPass(encoder, &renderPassDesc); wgpuRenderPassEncoderEnd(renderPass); WGPUCommandBufferDescriptor cmdBufferDescriptor = {}; cmdBufferDescriptor.nextInChain = nullptr; cmdBufferDescriptor.label = "Command buffer"; WGPUCommandBuffer command = wgpuCommandEncoderFinish(encoder, &cmdBufferDescriptor); wgpuQueueSubmit(mWebGPUQueue, 1, &command); mWebGPUSwapChain.present(); nextTexture.drop(); } void RWGPU::drawText(Resource* resource, std::string text, IntVec2 pos, Color color) {} void RWGPU::drawTexture(Resource* resource, IntRect size) {} void RWGPU::useShader(Shader* shader, Point2D points[3]) {} void RWGPU::stopUsingShaders() {} void RWGPU::preDraw() {} void RWGPU::postDraw() {} void RWGPU::update() { mKeepOpen = !glfwWindowShouldClose(mWindow); glfwPollEvents(); } void RWGPU::compileShader(Shader* shader) {} void RWGPU::toggleWireframe() {} void RWGPU::setWindowTitle(std::string title) { glfwSetWindowTitle(mWindow, title.c_str()); } void RWGPU::resize(unsigned int width, unsigned int height) { if (width < 0 || height < 0) return; glfwSetWindowSize(mWindow, width, height); setupSwapChain(width, height); } }