.
This commit is contained in:
1 parent
a0b7e4c0d8
commit
6883d103ff
36 files changed
+2736
-1428
No files matched your search
@@ -3,3 +3,278 @@
|
||||
//
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "webgpu.h"
|
||||
#include "../graphics.h"
|
||||
#include <misc.h>
|
||||
#include <print>
|
||||
|
||||
extern WGPUInstance instance;
|
||||
|
||||
extern Device wgpu_device;
|
||||
extern Queue wgpu_queue;
|
||||
|
||||
Renderer::Renderer(GLFWwindow *window) {
|
||||
create_compute_pipeline();
|
||||
create_render_pipeline();
|
||||
|
||||
// Number of floats in the buffers
|
||||
|
||||
WGPUBufferDescriptor inputBufferDesc = WGPU_BUFFER_DESCRIPTOR_INIT;
|
||||
inputBufferDesc.label = toWgpuStringView("Input Buffer");
|
||||
inputBufferDesc.size = elementCount * sizeof(float);
|
||||
inputBufferDesc.usage = WGPUBufferUsage_Storage;
|
||||
inputBufferDesc.mappedAtCreation = true;
|
||||
input_buffer = wgpuDeviceCreateBuffer(wgpu_device.device, &inputBufferDesc);
|
||||
|
||||
WGPUBufferDescriptor outputBufferDesc = WGPU_BUFFER_DESCRIPTOR_INIT;
|
||||
outputBufferDesc.label = toWgpuStringView("Output Buffer");
|
||||
outputBufferDesc.size = elementCount * sizeof(float);
|
||||
outputBufferDesc.usage = WGPUBufferUsage_Storage | WGPUBufferUsage_CopySrc;
|
||||
output_buffer = wgpuDeviceCreateBuffer(wgpu_device.device, &outputBufferDesc);
|
||||
|
||||
WGPUBufferDescriptor stagingBufferDesc = WGPU_BUFFER_DESCRIPTOR_INIT;
|
||||
stagingBufferDesc.label = toWgpuStringView("Staging Buffer");
|
||||
stagingBufferDesc.size = elementCount * sizeof(float);
|
||||
stagingBufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead;
|
||||
staging_buffer = wgpuDeviceCreateBuffer(wgpu_device.device, &stagingBufferDesc);
|
||||
|
||||
float* inputBufferData = static_cast<float*>(
|
||||
wgpuBufferGetMappedRange(input_buffer, 0, WGPU_WHOLE_MAP_SIZE)
|
||||
);
|
||||
// Write 0.0, 0.1, 0.2, 0.3, ... in inputBuffer
|
||||
for (size_t i = 0 ; i < elementCount ; ++i) {
|
||||
inputBufferData[i] = static_cast<float>(i) * 0.1f;
|
||||
}
|
||||
wgpuBufferUnmap(input_buffer);
|
||||
}
|
||||
|
||||
void Renderer::create_compute_pipeline() {
|
||||
std::string shader_source = read_entire_file("shader/compute.wgsl");
|
||||
|
||||
if (!shader_source.empty()) {
|
||||
WGPUShaderSourceWGSL wgslSourceDesc = WGPU_SHADER_SOURCE_WGSL_INIT;
|
||||
wgslSourceDesc.code = toWgpuStringView(shader_source);
|
||||
|
||||
WGPUShaderModuleDescriptor moduleDesc = WGPU_SHADER_MODULE_DESCRIPTOR_INIT;
|
||||
moduleDesc.nextInChain = &wgslSourceDesc.chain;
|
||||
moduleDesc.label = toWgpuStringView("Our first compute shader");
|
||||
|
||||
WGPUShaderModule shaderModule = wgpuDeviceCreateShaderModule(wgpu_device.device, &moduleDesc);
|
||||
|
||||
WGPUComputePipelineDescriptor desc = WGPU_COMPUTE_PIPELINE_DESCRIPTOR_INIT;
|
||||
desc.label = toWgpuStringView("Our simple pipeline");
|
||||
desc.compute.module = shaderModule;
|
||||
desc.compute.entryPoint = toWgpuStringView("main");
|
||||
|
||||
compute_pipeline = wgpuDeviceCreateComputePipeline(wgpu_device.device, &desc);
|
||||
} else {
|
||||
std::println("Couldn't load compute shader source");
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::create_render_pipeline() {
|
||||
|
||||
}
|
||||
|
||||
void Renderer::submit_sprite(glm::vec2 pos, const sprite_t &sprite) {
|
||||
RenderCommand cmd {};
|
||||
cmd.pipeline = PipelineType::TexturedQuad;
|
||||
cmd.key = {
|
||||
(uint16_t) pos.y,
|
||||
0,
|
||||
(uint8_t) PipelineType::TexturedQuad
|
||||
};
|
||||
|
||||
const Texture &texture = texture_manager.textures[sprite.texture];
|
||||
|
||||
cmd.textured_quad = {
|
||||
.pos = pos,
|
||||
.scale = { sprite.scale.x, sprite.scale.y },
|
||||
.uv0 = {0, 0},
|
||||
.uv1 = {1, 1},
|
||||
.colour = {1, 1, 1, 1},
|
||||
.texture = texture.p_texture->texture,
|
||||
};
|
||||
|
||||
commands.push_back(cmd);
|
||||
|
||||
// assert(started == true, "You can't submit without having started the renderer first.");
|
||||
// renderable : Renderable;
|
||||
// renderable.type = .Sprite;
|
||||
//
|
||||
// if sprite.window_space
|
||||
// renderable.projection_type = .ORTHOGRAPHIC_WINDOW;
|
||||
// else
|
||||
// renderable.projection_type = .ORTHOGRAPHIC_WORLD;
|
||||
//
|
||||
// renderable.pos = pos;
|
||||
// renderable.sprite.texture_sheet = sprite.texture_sheet;
|
||||
// renderable.sprite.texture_cell = sprite.texture_cell;
|
||||
// renderable.sprite.origin = sprite.origin;
|
||||
// renderable.sprite.scale = sprite.scale;
|
||||
// renderable.sprite.colour = sprite.colour;
|
||||
// renderable.sprite.alpha = alpha;
|
||||
//
|
||||
// array_add(*renderer.renderable_list, renderable);
|
||||
}
|
||||
|
||||
void Renderer::begin_frame() {
|
||||
commands.clear();
|
||||
}
|
||||
|
||||
uint32_t divideAndCeil(uint32_t p, uint32_t q) {
|
||||
return (p + q - 1) / q;
|
||||
}
|
||||
|
||||
void fetchBufferDataSync(
|
||||
WGPUInstance instance,
|
||||
WGPUBuffer bufferB,
|
||||
std::function<void(const void*)> processBufferData
|
||||
) {
|
||||
// We copy here what used to be in main():
|
||||
// Context passed to `onBufferBMapped` through theuserdata pointer:
|
||||
struct OnBufferBMappedContext {
|
||||
bool operationEnded = false; // Turned true as soon as the callback is invoked
|
||||
bool mappingIsSuccessful = false; // Turned true only if mapping succeeded
|
||||
};
|
||||
|
||||
// This function has the type WGPUBufferMapCallback as defined in webgpu.h
|
||||
auto onBufferBMapped = [](
|
||||
WGPUMapAsyncStatus status,
|
||||
struct WGPUStringView message,
|
||||
void* userdata1,
|
||||
void* /* userdata2 */
|
||||
) {
|
||||
OnBufferBMappedContext& context = *reinterpret_cast<OnBufferBMappedContext*>(userdata1);
|
||||
context.operationEnded = true;
|
||||
if (status == WGPUMapAsyncStatus_Success) {
|
||||
context.mappingIsSuccessful = true;
|
||||
} else {
|
||||
std::cout << "Could not map buffer B! Status: " << status << ", message: " << toStdStringView(message) << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
// We create an instance of the context shared with `onBufferBMapped`
|
||||
OnBufferBMappedContext context;
|
||||
|
||||
// And we build the callback info:
|
||||
WGPUBufferMapCallbackInfo callbackInfo = WGPU_BUFFER_MAP_CALLBACK_INFO_INIT;
|
||||
callbackInfo.mode = WGPUCallbackMode_AllowProcessEvents;
|
||||
callbackInfo.callback = onBufferBMapped;
|
||||
callbackInfo.userdata1 = &context;
|
||||
|
||||
// And finally we launch the asynchronous operation
|
||||
wgpuBufferMapAsync(
|
||||
bufferB,
|
||||
WGPUMapMode_Read,
|
||||
0, // offset
|
||||
WGPU_WHOLE_MAP_SIZE,
|
||||
callbackInfo
|
||||
);
|
||||
|
||||
// Process events until the map operation ended
|
||||
wgpuInstanceProcessEvents(instance);
|
||||
while (!context.operationEnded) {
|
||||
sleepForMilliseconds(200);
|
||||
wgpuInstanceProcessEvents(instance);
|
||||
}
|
||||
|
||||
if (context.mappingIsSuccessful) {
|
||||
const void* bufferData = wgpuBufferGetConstMappedRange(bufferB, 0, WGPU_WHOLE_MAP_SIZE);
|
||||
processBufferData(bufferData);
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::end_frame(GLFWwindow *window) {
|
||||
|
||||
std::vector<WGPUBindGroupEntry> bindGroupEntries(2, WGPU_BIND_GROUP_ENTRY_INIT);
|
||||
bindGroupEntries[0].binding = 0;
|
||||
bindGroupEntries[0].buffer = input_buffer;
|
||||
bindGroupEntries[1].binding = 1;
|
||||
bindGroupEntries[1].buffer = output_buffer;
|
||||
|
||||
WGPUBindGroupDescriptor bindGroupDesc = WGPU_BIND_GROUP_DESCRIPTOR_INIT;
|
||||
bindGroupDesc.entries = bindGroupEntries.data();
|
||||
bindGroupDesc.entryCount = bindGroupEntries.size();
|
||||
bindGroupDesc.layout = wgpuComputePipelineGetBindGroupLayout(compute_pipeline, 0);
|
||||
|
||||
WGPUBindGroup bindGroup = wgpuDeviceCreateBindGroup(wgpu_device.device, &bindGroupDesc);
|
||||
wgpuBindGroupLayoutRelease(bindGroupDesc.layout);
|
||||
|
||||
WGPUCommandEncoderDescriptor encoderDesc = WGPU_COMMAND_ENCODER_DESCRIPTOR_INIT;
|
||||
encoderDesc.label = toWgpuStringView("My command encoder");
|
||||
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device.device, &encoderDesc);
|
||||
WGPUComputePassEncoder computePass = wgpuCommandEncoderBeginComputePass(encoder, nullptr);
|
||||
wgpuComputePassEncoderSetPipeline(computePass, compute_pipeline);
|
||||
wgpuComputePassEncoderSetBindGroup(computePass, 0, bindGroup, 0, nullptr);
|
||||
uint32_t workgroupSizeX = 32; // the value specified in @workgroup_size(...)
|
||||
uint32_t workgroupCountX = divideAndCeil((uint32_t)elementCount, workgroupSizeX);
|
||||
// After the end of the compute pass, we copy the whole output buffer into the staging buffer
|
||||
wgpuComputePassEncoderDispatchWorkgroups(computePass, workgroupCountX, 1, 1);
|
||||
wgpuComputePassEncoderEnd(computePass);
|
||||
wgpuComputePassEncoderRelease(computePass);
|
||||
wgpuCommandEncoderCopyBufferToBuffer(encoder, output_buffer, 0, staging_buffer, 0, elementCount * sizeof(float));
|
||||
|
||||
WGPUCommandBufferDescriptor cmdBufferDescriptor = WGPU_COMMAND_BUFFER_DESCRIPTOR_INIT;
|
||||
cmdBufferDescriptor.label = toWgpuStringView("Command buffer");
|
||||
WGPUCommandBuffer command = wgpuCommandEncoderFinish(encoder, &cmdBufferDescriptor);
|
||||
wgpuCommandEncoderRelease(encoder); // release encoder after it's finished
|
||||
|
||||
// Finally submit the command queue
|
||||
std::cout << "Submitting command..." << std::endl;
|
||||
wgpuQueueSubmit(wgpu_queue.queue, 1, &command);
|
||||
wgpuCommandBufferRelease(command);
|
||||
std::cout << "Command submitted." << std::endl;
|
||||
// Removed
|
||||
fetchBufferDataSync(instance, staging_buffer, [&](const void* data) {
|
||||
const float* floatData = static_cast<const float*>(data);
|
||||
std::cout << "Result: [";
|
||||
for (size_t i = 0 ; i < elementCount ; ++i) {
|
||||
if (i > 0) std::cout << ", ";
|
||||
std::cout << floatData[i];
|
||||
}
|
||||
std::cout << "]" << std::endl;
|
||||
});
|
||||
|
||||
// // Get the next target texture view
|
||||
// WGPUTextureView target_view = get_next_surface_view();
|
||||
// if (!target_view) return; // no surface texture, we skip this frame
|
||||
//
|
||||
// WGPUCommandEncoderDescriptor encoderDesc = WGPU_COMMAND_ENCODER_DESCRIPTOR_INIT;
|
||||
// encoderDesc.label = toWgpuStringView("My command encoder");
|
||||
// WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device.device, &encoderDesc);
|
||||
// WGPURenderPassDescriptor renderPassDesc = WGPU_RENDER_PASS_DESCRIPTOR_INIT;
|
||||
// WGPURenderPassColorAttachment colorAttachment = WGPU_RENDER_PASS_COLOR_ATTACHMENT_INIT;
|
||||
//
|
||||
// colorAttachment.view = target_view;
|
||||
// colorAttachment.loadOp = WGPULoadOp_Clear;
|
||||
// colorAttachment.storeOp = WGPUStoreOp_Store;
|
||||
// colorAttachment.clearValue = WGPUColor{ 100.0 / 255.0, 149.0 / 255.0, 237.0 / 255.0, 1.0 };
|
||||
//
|
||||
// renderPassDesc.colorAttachmentCount = 1;
|
||||
// renderPassDesc.colorAttachments = &colorAttachment;
|
||||
//
|
||||
// WGPURenderPassEncoder renderPass = wgpuCommandEncoderBeginRenderPass(encoder, &renderPassDesc);
|
||||
// // Use the render pass here (we do nothing with the render pass for now)
|
||||
// wgpuRenderPassEncoderEnd(renderPass);
|
||||
// wgpuRenderPassEncoderRelease(renderPass);
|
||||
// WGPUCommandBufferDescriptor cmdBufferDescriptor = WGPU_COMMAND_BUFFER_DESCRIPTOR_INIT;
|
||||
// cmdBufferDescriptor.label = toWgpuStringView("Command buffer");
|
||||
// WGPUCommandBuffer command = wgpuCommandEncoderFinish(encoder, &cmdBufferDescriptor);
|
||||
// wgpuCommandEncoderRelease(encoder); // release encoder after it's finished
|
||||
//
|
||||
// // Finally submit the command queue
|
||||
// std::println("Submitting command...");
|
||||
// wgpuQueueSubmit(wgpu_queue.queue, 1, &command);
|
||||
// wgpuCommandBufferRelease(command);
|
||||
// std::println("Command submitted.");
|
||||
//
|
||||
// // At the end of the frame
|
||||
// wgpuTextureViewRelease(target_view);
|
||||
// #ifndef __EMSCRIPTEN__
|
||||
// wgpuSurfacePresent(wgpu_surface.surface);
|
||||
// #endif
|
||||
}
|
||||
@@ -5,12 +5,102 @@
|
||||
#ifndef V_RENDERER_H
|
||||
#define V_RENDERER_H
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <GLFW/emscripten_glfw3.h>
|
||||
#else
|
||||
#include <GLFW/glfw3.h>
|
||||
#endif
|
||||
#include <glm/glm.hpp>
|
||||
#include <webgpu/webgpu.h>
|
||||
|
||||
#include "../sprite.h"
|
||||
|
||||
class renderer {
|
||||
enum class PROJECTION_TYPE : uint8_t {
|
||||
NONE,
|
||||
ORTHOGRAPHIC_WORLD,
|
||||
ORTHOGRAPHIC_WINDOW,
|
||||
PERSPECTIVE_WORLD,
|
||||
PERSPECTIVE_WINDOW,
|
||||
COUNT,
|
||||
};
|
||||
|
||||
// commands
|
||||
|
||||
enum class PipelineType : uint8_t {
|
||||
None,
|
||||
TexturedQuad,
|
||||
ColoredQuad,
|
||||
Line,
|
||||
Text,
|
||||
Chunk
|
||||
};
|
||||
|
||||
struct TexturedQuadCmd {
|
||||
glm::vec2 pos;
|
||||
glm::vec2 scale;
|
||||
glm::vec2 uv0;
|
||||
glm::vec2 uv1;
|
||||
glm::vec4 colour;
|
||||
WGPUTextureDescriptor texture;
|
||||
};
|
||||
|
||||
struct ColoredQuadCmd {
|
||||
glm::vec2 pos;
|
||||
glm::vec2 scale;
|
||||
glm::vec4 colour;
|
||||
};
|
||||
|
||||
struct LineCmd {
|
||||
glm::vec2 start;
|
||||
glm::vec2 end;
|
||||
glm::vec4 color;
|
||||
};
|
||||
|
||||
|
||||
struct SortKey {
|
||||
uint16_t depth; // world Z or Y-sorted depth
|
||||
uint16_t materialID; // texture sheet, font atlas, etc.
|
||||
uint8_t pipeline; // PipelineType
|
||||
|
||||
#endif //V_RENDERER_H
|
||||
bool operator<(const SortKey& b) const;
|
||||
};
|
||||
|
||||
struct RenderCommand {
|
||||
|
||||
SortKey key;
|
||||
PipelineType pipeline;
|
||||
|
||||
union {
|
||||
TexturedQuadCmd textured_quad;
|
||||
ColoredQuadCmd colored_quad;
|
||||
LineCmd line;
|
||||
// TextCmd text;
|
||||
// ChunkCmd chunk;
|
||||
};
|
||||
};
|
||||
|
||||
struct Renderer {
|
||||
std::vector<RenderCommand> commands{};
|
||||
|
||||
WGPURenderPipeline textured_quad_pipeline;
|
||||
|
||||
size_t elementCount = 64;
|
||||
WGPUComputePipeline compute_pipeline;
|
||||
|
||||
WGPUBuffer input_buffer;
|
||||
WGPUBuffer output_buffer;
|
||||
WGPUBuffer staging_buffer;
|
||||
|
||||
Renderer() = default;
|
||||
Renderer(GLFWwindow *window);
|
||||
|
||||
void begin_frame();
|
||||
void end_frame(GLFWwindow *window);
|
||||
void submit_sprite(glm::vec2 pos, const sprite_t &sprite);
|
||||
|
||||
void create_render_pipeline();
|
||||
void create_compute_pipeline();
|
||||
};
|
||||
|
||||
|
||||
#endif //V_RENDERER_H
|
||||
@@ -33,47 +33,8 @@
|
||||
#error "utils_emscripten.cpp: This file requires EMSCRIPTEN to be defined."
|
||||
#endif // !defined(EMSCRIPTEN)
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "GLFW/glfw3.h"
|
||||
#include "emscripten/emscripten.h"
|
||||
#include "webgpu/webgpu_glfw.h"
|
||||
|
||||
WGPU_GLFW_EXPORT WGPUSurface wgpuGlfwCreateSurfaceForWindow(const WGPUInstance instance,
|
||||
GLFWwindow* window) {
|
||||
wgpu::Surface s = wgpu::glfw::CreateSurfaceForWindow(instance, window);
|
||||
return s.MoveToCHandle();
|
||||
}
|
||||
|
||||
namespace wgpu::glfw {
|
||||
|
||||
wgpu::Surface CreateSurfaceForWindow(const wgpu::Instance& instance, GLFWwindow* window) {
|
||||
auto chainedDescriptor = SetupWindowAndGetSurfaceDescriptor(window);
|
||||
|
||||
wgpu::SurfaceDescriptor descriptor;
|
||||
descriptor.nextInChain = chainedDescriptor.get();
|
||||
wgpu::Surface surface = instance.CreateSurface(&descriptor);
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
std::unique_ptr<wgpu::ChainedStruct, void (*)(wgpu::ChainedStruct*)>
|
||||
SetupWindowAndGetSurfaceDescriptor(GLFWwindow* window) {
|
||||
if (glfwGetWindowAttrib(window, GLFW_CLIENT_API) != GLFW_NO_API) {
|
||||
emscripten_log(EM_LOG_ERROR,
|
||||
"GL context was created on the window. Disable context creation by "
|
||||
"setting the GLFW_CLIENT_API hint to GLFW_NO_API.");
|
||||
return {nullptr, [](wgpu::ChainedStruct*) {}};
|
||||
}
|
||||
|
||||
wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector* desc =
|
||||
new wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector();
|
||||
// Map "!canvas" CSS selector to the canvas held in the Module.canvas object.
|
||||
EM_ASM({self.specialHTMLTargets && (specialHTMLTargets["!canvas"] = Module.canvas)});
|
||||
desc->selector = "!canvas";
|
||||
return {desc, [](wgpu::ChainedStruct* desc) {
|
||||
delete reinterpret_cast<wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector*>(desc);
|
||||
}};
|
||||
}
|
||||
|
||||
} // namespace wgpu::glfw
|
||||
GLFWwindow* window);
|
||||
@@ -3,3 +3,425 @@
|
||||
//
|
||||
|
||||
#include "webgpu.h"
|
||||
#include <webgpu/webgpu.h>
|
||||
#include <dawn/webgpu_cpp_print.h>
|
||||
#include "renderer.h"
|
||||
#include "../graphics.h"
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <iostream>
|
||||
#include "utils_emscripten.h"
|
||||
|
||||
WGPUInstance instance;
|
||||
WGPUAdapter adapter;
|
||||
Device wgpu_device;
|
||||
Queue wgpu_queue;
|
||||
Surface wgpu_surface;
|
||||
|
||||
Renderer renderer;
|
||||
|
||||
void upload_texture(
|
||||
const int w,
|
||||
const int h,
|
||||
const void *pixels,
|
||||
Texture *texture)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
std::string_view toStdStringView(WGPUStringView wgpuStringView) {
|
||||
return
|
||||
wgpuStringView.data == nullptr
|
||||
? std::string_view()
|
||||
: wgpuStringView.length == WGPU_STRLEN
|
||||
? std::string_view(wgpuStringView.data)
|
||||
: std::string_view(wgpuStringView.data, wgpuStringView.length);
|
||||
}
|
||||
|
||||
WGPUStringView toWgpuStringView(std::string_view stdStringView) {
|
||||
return { stdStringView.data(), stdStringView.size() };
|
||||
}
|
||||
WGPUStringView toWgpuStringView(const char* cString) {
|
||||
return { cString, WGPU_STRLEN };
|
||||
}
|
||||
|
||||
void sleepForMilliseconds(unsigned int milliseconds) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
emscripten_sleep(milliseconds);
|
||||
#else
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to get a WebGPU adapter, so that
|
||||
* WGPUAdapter adapter = requestAdapterSync(options);
|
||||
* is roughly equivalent to the JavaScript
|
||||
* const adapter = await navigator.gpu.requestAdapter(options);
|
||||
*/
|
||||
WGPUAdapter requestAdapterSync(WGPUInstance instance, 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 userdata1 pointer,
|
||||
// provided as the last argument of wgpuInstanceRequestAdapter and received
|
||||
// by the callback as its last argument.
|
||||
auto onAdapterRequestEnded = [](
|
||||
WGPURequestAdapterStatus status,
|
||||
WGPUAdapter adapter,
|
||||
WGPUStringView message,
|
||||
void* userdata1,
|
||||
void* /* userdata2 */
|
||||
) {
|
||||
UserData& userData = *reinterpret_cast<UserData*>(userdata1);
|
||||
if (status == WGPURequestAdapterStatus_Success) {
|
||||
userData.adapter = adapter;
|
||||
} else {
|
||||
std::cerr << "Error while requesting adapter: " << toStdStringView(message) << std::endl;
|
||||
}
|
||||
userData.requestEnded = true;
|
||||
};
|
||||
|
||||
// Build the callback info
|
||||
WGPURequestAdapterCallbackInfo callbackInfo = {
|
||||
/* nextInChain = */ nullptr,
|
||||
/* mode = */ WGPUCallbackMode_AllowProcessEvents,
|
||||
/* callback = */ onAdapterRequestEnded,
|
||||
/* userdata1 = */ &userData,
|
||||
/* userdata2 = */ nullptr
|
||||
};
|
||||
|
||||
// Call to the WebGPU request adapter procedure
|
||||
wgpuInstanceRequestAdapter(instance, options, callbackInfo);
|
||||
|
||||
// We wait until userData.requestEnded gets true
|
||||
|
||||
// Hand the execution to the WebGPU instance so that it can check for
|
||||
// pending async operations, in which case it invokes our callbacks.
|
||||
// NB: We test once before the loop not to wait for 200ms in case it is
|
||||
// already ready
|
||||
wgpuInstanceProcessEvents(instance);
|
||||
|
||||
while (!userData.requestEnded) {
|
||||
// Waiting for 200 ms to avoid asking too often to process events
|
||||
sleepForMilliseconds(200);
|
||||
|
||||
wgpuInstanceProcessEvents(instance);
|
||||
}
|
||||
|
||||
return userData.adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to get a WebGPU device, so that
|
||||
* WGPUDevice device = requestDeviceSync(adapter, options);
|
||||
* is roughly equivalent to
|
||||
* const device = await adapter.requestDevice(descriptor);
|
||||
* It is very similar to requestAdapter
|
||||
*/
|
||||
WGPUDevice requestDeviceSync(WGPUInstance instance, WGPUAdapter adapter, WGPUDeviceDescriptor const * descriptor) {
|
||||
struct UserData {
|
||||
WGPUDevice device = nullptr;
|
||||
bool requestEnded = false;
|
||||
};
|
||||
UserData userData;
|
||||
|
||||
// The callback
|
||||
auto onDeviceRequestEnded = [](
|
||||
WGPURequestDeviceStatus status,
|
||||
WGPUDevice device,
|
||||
WGPUStringView message,
|
||||
void* userdata1,
|
||||
void* /* userdata2 */
|
||||
) {
|
||||
UserData& userData = *reinterpret_cast<UserData*>(userdata1);
|
||||
if (status == WGPURequestDeviceStatus_Success) {
|
||||
userData.device = device;
|
||||
} else {
|
||||
std::cerr << "Error while requesting device: " << toStdStringView(message) << std::endl;
|
||||
}
|
||||
userData.requestEnded = true;
|
||||
};
|
||||
|
||||
// Build the callback info
|
||||
WGPURequestDeviceCallbackInfo callbackInfo = {
|
||||
/* nextInChain = */ nullptr,
|
||||
/* mode = */ WGPUCallbackMode_AllowProcessEvents,
|
||||
/* callback = */ onDeviceRequestEnded,
|
||||
/* userdata1 = */ &userData,
|
||||
/* userdata2 = */ nullptr
|
||||
};
|
||||
|
||||
// Call to the WebGPU request adapter procedure
|
||||
wgpuAdapterRequestDevice(adapter, descriptor, callbackInfo);
|
||||
|
||||
// Hand the execution to the WebGPU instance until the request ended
|
||||
wgpuInstanceProcessEvents(instance);
|
||||
while (!userData.requestEnded) {
|
||||
sleepForMilliseconds(200);
|
||||
wgpuInstanceProcessEvents(instance);
|
||||
}
|
||||
|
||||
return userData.device;
|
||||
}
|
||||
|
||||
void inspectAdapter(WGPUAdapter adapter) {
|
||||
WGPULimits supportedLimits = {};
|
||||
supportedLimits.nextInChain = nullptr;
|
||||
|
||||
bool success = wgpuAdapterGetLimits(adapter, &supportedLimits) == WGPUStatus_Success;
|
||||
|
||||
if (success) {
|
||||
std::cout << "Adapter limits:" << std::endl;
|
||||
std::cout << " - maxTextureDimension1D: " << supportedLimits.maxTextureDimension1D << std::endl;
|
||||
std::cout << " - maxTextureDimension2D: " << supportedLimits.maxTextureDimension2D << std::endl;
|
||||
std::cout << " - maxTextureDimension3D: " << supportedLimits.maxTextureDimension3D << std::endl;
|
||||
std::cout << " - maxTextureArrayLayers: " << supportedLimits.maxTextureArrayLayers << std::endl;
|
||||
}
|
||||
|
||||
// Prepare the struct where features will be listed
|
||||
WGPUSupportedFeatures features;
|
||||
|
||||
// Get adapter features. This may allocate memory that we must later free with wgpuSupportedFeaturesFreeMembers()
|
||||
wgpuAdapterGetFeatures(adapter, &features);
|
||||
|
||||
std::cout << "Adapter features:" << std::endl;
|
||||
std::cout << std::hex; // Write integers as hexadecimal to ease comparison with webgpu.h literals
|
||||
for (size_t i = 0; i < features.featureCount; ++i) {
|
||||
std::cout << " - 0x" << features.features[i] << std::endl;
|
||||
}
|
||||
std::cout << std::dec; // Restore decimal numbers
|
||||
|
||||
// Free the memory that had potentially been allocated by wgpuAdapterGetFeatures()
|
||||
wgpuSupportedFeaturesFreeMembers(features);
|
||||
// One shall no longer use features beyond this line.
|
||||
|
||||
WGPUAdapterInfo properties;
|
||||
properties.nextInChain = nullptr;
|
||||
wgpuAdapterGetInfo(adapter, &properties);
|
||||
std::cout << "Adapter properties:" << std::endl;
|
||||
std::cout << " - vendorID: " << properties.vendorID << std::endl;
|
||||
std::cout << " - vendorName: " << toStdStringView(properties.vendor) << std::endl;
|
||||
std::cout << " - architecture: " << toStdStringView(properties.architecture) << std::endl;
|
||||
std::cout << " - deviceID: " << properties.deviceID << std::endl;
|
||||
std::cout << " - name: " << toStdStringView(properties.device) << std::endl;
|
||||
std::cout << " - driverDescription: " << toStdStringView(properties.description) << std::endl;
|
||||
std::cout << std::hex;
|
||||
std::cout << " - adapterType: 0x" << properties.adapterType << std::endl;
|
||||
std::cout << " - backendType: 0x" << properties.backendType << std::endl;
|
||||
std::cout << std::dec; // Restore decimal numbers
|
||||
wgpuAdapterInfoFreeMembers(properties);
|
||||
}
|
||||
|
||||
// We create a utility function to inspect the device:
|
||||
void inspectDevice(WGPUDevice device) {
|
||||
|
||||
WGPUSupportedFeatures features = WGPU_SUPPORTED_FEATURES_INIT;
|
||||
wgpuDeviceGetFeatures(device, &features);
|
||||
std::cout << "Device features:" << std::endl;
|
||||
std::cout << std::hex;
|
||||
for (size_t i = 0; i < features.featureCount; ++i) {
|
||||
std::cout << " - 0x" << features.features[i] << std::endl;
|
||||
}
|
||||
std::cout << std::dec;
|
||||
wgpuSupportedFeaturesFreeMembers(features);
|
||||
|
||||
WGPULimits limits = WGPU_LIMITS_INIT;
|
||||
bool success = wgpuDeviceGetLimits(device, &limits) == WGPUStatus_Success;
|
||||
|
||||
if (success) {
|
||||
std::cout << "Device limits:" << std::endl;
|
||||
std::cout << " - maxTextureDimension1D: " << limits.maxTextureDimension1D << std::endl;
|
||||
std::cout << " - maxTextureDimension2D: " << limits.maxTextureDimension2D << std::endl;
|
||||
std::cout << " - maxTextureDimension3D: " << limits.maxTextureDimension3D << std::endl;
|
||||
std::cout << " - maxTextureArrayLayers: " << limits.maxTextureArrayLayers << std::endl;
|
||||
std::cout << " - maxBindGroups: " << limits.maxBindGroups << std::endl;
|
||||
std::cout << " - maxBindGroupsPlusVertexBuffers: " << limits.maxBindGroupsPlusVertexBuffers << std::endl;
|
||||
std::cout << " - maxBindingsPerBindGroup: " << limits.maxBindingsPerBindGroup << std::endl;
|
||||
std::cout << " - maxDynamicUniformBuffersPerPipelineLayout: " << limits.maxDynamicUniformBuffersPerPipelineLayout << std::endl;
|
||||
std::cout << " - maxDynamicStorageBuffersPerPipelineLayout: " << limits.maxDynamicStorageBuffersPerPipelineLayout << std::endl;
|
||||
std::cout << " - maxSampledTexturesPerShaderStage: " << limits.maxSampledTexturesPerShaderStage << std::endl;
|
||||
std::cout << " - maxSamplersPerShaderStage: " << limits.maxSamplersPerShaderStage << std::endl;
|
||||
std::cout << " - maxStorageBuffersPerShaderStage: " << limits.maxStorageBuffersPerShaderStage << std::endl;
|
||||
std::cout << " - maxStorageTexturesPerShaderStage: " << limits.maxStorageTexturesPerShaderStage << std::endl;
|
||||
std::cout << " - maxUniformBuffersPerShaderStage: " << limits.maxUniformBuffersPerShaderStage << std::endl;
|
||||
std::cout << " - maxUniformBufferBindingSize: " << limits.maxUniformBufferBindingSize << std::endl;
|
||||
std::cout << " - maxStorageBufferBindingSize: " << limits.maxStorageBufferBindingSize << std::endl;
|
||||
std::cout << " - minUniformBufferOffsetAlignment: " << limits.minUniformBufferOffsetAlignment << std::endl;
|
||||
std::cout << " - minStorageBufferOffsetAlignment: " << limits.minStorageBufferOffsetAlignment << std::endl;
|
||||
std::cout << " - maxVertexBuffers: " << limits.maxVertexBuffers << std::endl;
|
||||
std::cout << " - maxBufferSize: " << limits.maxBufferSize << std::endl;
|
||||
std::cout << " - maxVertexAttributes: " << limits.maxVertexAttributes << std::endl;
|
||||
std::cout << " - maxVertexBufferArrayStride: " << limits.maxVertexBufferArrayStride << std::endl;
|
||||
std::cout << " - maxInterStageShaderVariables: " << limits.maxInterStageShaderVariables << std::endl;
|
||||
std::cout << " - maxColorAttachments: " << limits.maxColorAttachments << std::endl;
|
||||
std::cout << " - maxColorAttachmentBytesPerSample: " << limits.maxColorAttachmentBytesPerSample << std::endl;
|
||||
std::cout << " - maxComputeWorkgroupStorageSize: " << limits.maxComputeWorkgroupStorageSize << std::endl;
|
||||
std::cout << " - maxComputeInvocationsPerWorkgroup: " << limits.maxComputeInvocationsPerWorkgroup << std::endl;
|
||||
std::cout << " - maxComputeWorkgroupSizeX: " << limits.maxComputeWorkgroupSizeX << std::endl;
|
||||
std::cout << " - maxComputeWorkgroupSizeY: " << limits.maxComputeWorkgroupSizeY << std::endl;
|
||||
std::cout << " - maxComputeWorkgroupSizeZ: " << limits.maxComputeWorkgroupSizeZ << std::endl;
|
||||
std::cout << " - maxComputeWorkgroupsPerDimension: " << limits.maxComputeWorkgroupsPerDimension << std::endl;
|
||||
// std::cout << " - maxStorageBuffersInVertexStage: " << limits.maxStorageBuffersInVertexStage << std::endl;
|
||||
// std::cout << " - maxStorageTexturesInVertexStage: " << limits.maxStorageTexturesInVertexStage << std::endl;
|
||||
// std::cout << " - maxStorageBuffersInFragmentStage: " << limits.maxStorageBuffersInFragmentStage << std::endl;
|
||||
// std::cout << " - maxStorageTexturesInFragmentStage: " << limits.maxStorageTexturesInFragmentStage << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void create_surface(GLFWwindow *window) {
|
||||
wgpu_surface.surface = wgpuGlfwCreateSurfaceForWindow(instance, window);
|
||||
|
||||
WGPUSurfaceConfiguration config = WGPU_SURFACE_CONFIGURATION_INIT;
|
||||
config.width = 640;
|
||||
config.height = 480;
|
||||
config.device = wgpu_device.device;
|
||||
|
||||
// We initialize an empty capability struct:
|
||||
WGPUSurfaceCapabilities capabilities = WGPU_SURFACE_CAPABILITIES_INIT;
|
||||
|
||||
// We get the capabilities for a pair of (surface, adapter).
|
||||
// If it works, this populates the `capabilities` structure
|
||||
WGPUStatus status = wgpuSurfaceGetCapabilities(wgpu_surface.surface, adapter, &capabilities);
|
||||
if (status != WGPUStatus_Success) {
|
||||
return;
|
||||
}
|
||||
|
||||
// From the capabilities, we get the preferred format: it is always the first one!
|
||||
// (NB: There is always at least 1 format if the GetCapabilities was successful)
|
||||
config.format = capabilities.formats[0];
|
||||
|
||||
// We no longer need to access the capabilities, so we release their memory.
|
||||
wgpuSurfaceCapabilitiesFreeMembers(capabilities);
|
||||
|
||||
wgpuSurfaceConfigure(wgpu_surface.surface, &config);
|
||||
}
|
||||
|
||||
WGPUTextureView get_next_surface_view() {
|
||||
WGPUSurfaceTexture surfaceTexture = WGPU_SURFACE_TEXTURE_INIT;
|
||||
wgpuSurfaceGetCurrentTexture(wgpu_surface.surface, &surfaceTexture);
|
||||
|
||||
WGPUTextureViewDescriptor viewDescriptor = WGPU_TEXTURE_VIEW_DESCRIPTOR_INIT;
|
||||
viewDescriptor.label = toWgpuStringView("Surface texture view");
|
||||
viewDescriptor.dimension = WGPUTextureViewDimension_2D; // not to confuse with 2DArray
|
||||
WGPUTextureView target_view = wgpuTextureCreateView(surfaceTexture.texture, &viewDescriptor);
|
||||
|
||||
// We no longer need the texture, only its view,
|
||||
// so we release it at the end of GetNextSurfaceViewData
|
||||
wgpuTextureRelease(surfaceTexture.texture);
|
||||
return target_view;
|
||||
}
|
||||
|
||||
void create_device() {
|
||||
std::cout << "Requesting adapter..." << std::endl;
|
||||
|
||||
WGPURequestAdapterOptions adapterOpts = {};
|
||||
adapterOpts.nextInChain = nullptr;
|
||||
adapter = requestAdapterSync(instance, &adapterOpts);
|
||||
|
||||
std::cout << "Got adapter: " << adapter << std::endl;
|
||||
|
||||
inspectAdapter(adapter);
|
||||
|
||||
std::cout << "Requesting device..." << std::endl;
|
||||
|
||||
WGPUDeviceDescriptor deviceDesc = WGPU_DEVICE_DESCRIPTOR_INIT;
|
||||
// Any name works here, that's your call
|
||||
deviceDesc.label = toWgpuStringView("My Device");
|
||||
std::vector<WGPUFeatureName> features;
|
||||
// No required feature for now
|
||||
deviceDesc.requiredFeatureCount = features.size();
|
||||
deviceDesc.requiredFeatures = features.data();
|
||||
// Make sure 'features' lives until the call to wgpuAdapterRequestDevice!
|
||||
WGPULimits requiredLimits = WGPU_LIMITS_INIT;
|
||||
// We leave 'requiredLimits' untouched for now
|
||||
deviceDesc.requiredLimits = &requiredLimits;
|
||||
// Make sure that the 'requiredLimits' variable lives until the call to wgpuAdapterRequestDevice!
|
||||
deviceDesc.defaultQueue.label = toWgpuStringView("The Default Queue");
|
||||
auto onDeviceLost = [](
|
||||
WGPUDevice const * device,
|
||||
WGPUDeviceLostReason reason,
|
||||
struct WGPUStringView message,
|
||||
void* /* userdata1 */,
|
||||
void* /* userdata2 */
|
||||
) {
|
||||
// All we do is display a message when the device is lost
|
||||
std::cout
|
||||
<< "Device " << device << " was lost: reason " << reason
|
||||
<< " (" << toStdStringView(message) << ")"
|
||||
<< std::endl;
|
||||
};
|
||||
deviceDesc.deviceLostCallbackInfo.callback = onDeviceLost;
|
||||
deviceDesc.deviceLostCallbackInfo.mode = WGPUCallbackMode_AllowProcessEvents;
|
||||
auto onDeviceError = [](
|
||||
WGPUDevice const * device,
|
||||
WGPUErrorType type,
|
||||
struct WGPUStringView message,
|
||||
void* /* userdata1 */,
|
||||
void* /* userdata2 */
|
||||
) {
|
||||
std::cout
|
||||
<< "Uncaptured error in device " << device << ": type " << type
|
||||
<< " (" << toStdStringView(message) << ")"
|
||||
<< std::endl;
|
||||
};
|
||||
deviceDesc.uncapturedErrorCallbackInfo.callback = onDeviceError;
|
||||
wgpu_device.device = requestDeviceSync(instance, adapter, &deviceDesc);
|
||||
|
||||
std::cout << "Got device: " << wgpu_device.device << std::endl;
|
||||
// We no longer need to access the adapter once we have the device
|
||||
wgpuAdapterRelease(adapter);
|
||||
inspectDevice(wgpu_device.device);
|
||||
|
||||
wgpu_queue.queue = wgpuDeviceGetQueue(wgpu_device.device);
|
||||
}
|
||||
|
||||
void create_instance() {
|
||||
// We create a descriptor
|
||||
WGPUInstanceDescriptor desc = WGPU_INSTANCE_DESCRIPTOR_INIT;
|
||||
desc.nextInChain = nullptr;
|
||||
|
||||
// We create the instance using this descriptor
|
||||
#ifdef __EMSCRIPTEN__
|
||||
instance = wgpuCreateInstance(nullptr);
|
||||
#else
|
||||
instance = wgpuCreateInstance(&desc);
|
||||
#endif
|
||||
}
|
||||
|
||||
void platform_graphics_init(GLFWwindow *window) {
|
||||
create_instance();
|
||||
|
||||
create_device();
|
||||
|
||||
create_surface(window);
|
||||
|
||||
renderer = Renderer(window);
|
||||
}
|
||||
|
||||
void graphics_deinit() {
|
||||
|
||||
}
|
||||
|
||||
void begin_frame() {
|
||||
renderer.begin_frame();
|
||||
}
|
||||
|
||||
void end_frame(GLFWwindow *window) {
|
||||
|
||||
renderer.end_frame(window);
|
||||
}
|
||||
|
||||
void submit_sprite(glm::vec2 pos, const sprite_t &sprite) {
|
||||
renderer.submit_sprite({pos.x, pos.y}, sprite);
|
||||
}
|
||||
@@ -5,12 +5,34 @@
|
||||
#ifndef V_WEBGPU_H
|
||||
#define V_WEBGPU_H
|
||||
|
||||
#include <string_view>
|
||||
#include <webgpu/webgpu.h>
|
||||
|
||||
|
||||
class webgpu {
|
||||
|
||||
struct Device {
|
||||
WGPUDevice device;
|
||||
};
|
||||
|
||||
struct PlatformTexture {
|
||||
WGPUTextureDescriptor texture;
|
||||
};
|
||||
|
||||
struct Queue {
|
||||
WGPUQueue queue;
|
||||
};
|
||||
|
||||
struct Surface {
|
||||
WGPUSurface surface;
|
||||
};
|
||||
|
||||
std::string_view toStdStringView(WGPUStringView wgpuStringView);
|
||||
|
||||
WGPUStringView toWgpuStringView(std::string_view stdStringView);
|
||||
|
||||
WGPUStringView toWgpuStringView(const char* cString);
|
||||
|
||||
WGPUTextureView get_next_surface_view();
|
||||
|
||||
void sleepForMilliseconds(unsigned int milliseconds);
|
||||
|
||||
|
||||
#endif //V_WEBGPU_H
|
||||
#endif //V_WEBGPU_H
|
||||
Reference in new issue
Block a user