Skip to content

Commit

Permalink
Sync with guide
Browse files Browse the repository at this point in the history
  • Loading branch information
eliemichel committed Oct 5, 2024
1 parent bc07237 commit 1a07c85
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 79 deletions.
22 changes: 22 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,32 @@ add_subdirectory(glfw3webgpu)

add_executable(App
main.cpp
# We add the ResourceManager files
ResourceManager.h
ResourceManager.cpp
)

target_link_libraries(App PRIVATE glfw webgpu glfw3webgpu)

# We add an option to enable different settings when developing the app than
# when distributing it.
option(DEV_MODE "Set up development helper settings" ON)

if(DEV_MODE)
# In dev mode, we load resources from the source tree, so that when we
# dynamically edit resources (like shaders), these are correctly
# versionned.
target_compile_definitions(App PRIVATE
RESOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/resources"
)
else()
# In release mode, we just load resources relatively to wherever the
# executable is launched from, so that the binary is portable
target_compile_definitions(App PRIVATE
RESOURCE_DIR="./resources"
)
endif()

target_copy_webgpu_binaries(App)

set_target_properties(App PROPERTIES
Expand Down
93 changes: 93 additions & 0 deletions ResourceManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// In ResourceManager.cpp
#include "ResourceManager.h"

#include <fstream>
#include <sstream>
#include <string>

using namespace wgpu;

bool ResourceManager::loadGeometry(
const std::filesystem::path& path,
std::vector<float>& pointData,
std::vector<uint16_t>& indexData
) {
std::ifstream file(path);
if (!file.is_open()) {
return false;
}

pointData.clear();
indexData.clear();

enum class Section {
None,
Points,
Indices,
};
Section currentSection = Section::None;

float value;
uint16_t index;
std::string line;
while (!file.eof()) {
getline(file, line);

// overcome the `CRLF` problem
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}

if (line == "[points]") {
currentSection = Section::Points;
}
else if (line == "[indices]") {
currentSection = Section::Indices;
}
else if (line[0] == '#' || line.empty()) {
// Do nothing, this is a comment
}
else if (currentSection == Section::Points) {
std::istringstream iss(line);
// Get x, y, r, g, b
for (int i = 0; i < 5; ++i) {
iss >> value;
pointData.push_back(value);
}
}
else if (currentSection == Section::Indices) {
std::istringstream iss(line);
// Get corners #0 #1 and #2
for (int i = 0; i < 3; ++i) {
iss >> index;
indexData.push_back(index);
}
}
}
return true;
}

ShaderModule ResourceManager::loadShaderModule(const std::filesystem::path& path, Device device) {
std::ifstream file(path);
if (!file.is_open()) {
return nullptr;
}
file.seekg(0, std::ios::end);
size_t size = file.tellg();
std::string shaderSource(size, ' ');
file.seekg(0);
file.read(shaderSource.data(), size);

ShaderModuleWGSLDescriptor shaderCodeDesc{};
shaderCodeDesc.chain.next = nullptr;
shaderCodeDesc.chain.sType = SType::ShaderModuleWGSLDescriptor;
shaderCodeDesc.code = shaderSource.c_str();

ShaderModuleDescriptor shaderDesc{};
#ifdef WEBGPU_BACKEND_WGPU
shaderDesc.hintCount = 0;
shaderDesc.hints = nullptr;
#endif
shaderDesc.nextInChain = &shaderCodeDesc.chain;
return device.createShaderModule(shaderDesc);
}
26 changes: 26 additions & 0 deletions ResourceManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once
#include <vector>
#include <filesystem>
#include <webgpu/webgpu.hpp>

class ResourceManager {
public:
/**
* Load a file from `path` using our ad-hoc format and populate the `pointData`
* and `indexData` vectors.
*/
static bool loadGeometry(
const std::filesystem::path& path,
std::vector<float>& pointData,
std::vector<uint16_t>& indexData
);

/**
* Create a shader module for a given WebGPU `device` from a WGSL shader source
* loaded from file `path`.
*/
static wgpu::ShaderModule loadShaderModule(
const std::filesystem::path& path,
wgpu::Device device
);
};
105 changes: 26 additions & 79 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,9 @@
#include <cassert>
#include <vector>

using namespace wgpu;

// We embbed the source of the shader module here
const char* shaderSource = R"(
/**
* A structure with fields labeled with vertex attribute locations can be used
* as input to the entry point of a shader.
*/
struct VertexInput {
@location(0) position: vec2f,
@location(1) color: vec3f,
};
/**
* A structure with fields labeled with builtins and locations can also be used
* as *output* of the vertex shader, which is also the input of the fragment
* shader.
*/
struct VertexOutput {
@builtin(position) position: vec4f,
// The location here does not refer to a vertex attribute, it just means
// that this field must be handled by the rasterizer.
// (It can also refer to another field of another struct that would be used
// as input to the fragment shader.)
@location(0) color: vec3f,
};
#include "ResourceManager.h"

@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
// ^^^^^^^^^^^^ We return a custom struct
var out: VertexOutput; // create the output struct
let ratio = 640.0 / 480.0; // The width and height of the target surface
out.position = vec4f(in.position.x, in.position.y * ratio, 0.0, 1.0);
out.color = in.color; // forward the color attribute to the fragment shader
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4f {
// ^^^^^^^^^^^^^^^^ Use for instance the same struct as what the vertex outputs
return vec4f(in.color, 1.0); // use the interpolated color coming from the vertex shader
}
)";
using namespace wgpu;

class Application {
public:
Expand Down Expand Up @@ -306,22 +266,15 @@ TextureView Application::GetNextSurfaceTextureView() {
}

void Application::InitializePipeline() {
// Load the shader module
ShaderModuleDescriptor shaderDesc;
#ifdef WEBGPU_BACKEND_WGPU
shaderDesc.hintCount = 0;
shaderDesc.hints = nullptr;
#endif

// We use the extension mechanism to specify the WGSL part of the shader module descriptor
ShaderModuleWGSLDescriptor shaderCodeDesc;
// Set the chained struct's header
shaderCodeDesc.chain.next = nullptr;
shaderCodeDesc.chain.sType = SType::ShaderModuleWGSLDescriptor;
// Connect the chain
shaderDesc.nextInChain = &shaderCodeDesc.chain;
shaderCodeDesc.code = shaderSource;
ShaderModule shaderModule = device.createShaderModule(shaderDesc);
std::cout << "Creating shader module..." << std::endl;
ShaderModule shaderModule = ResourceManager::loadShaderModule(RESOURCE_DIR "/shader.wgsl", device);
std::cout << "Shader module: " << shaderModule << std::endl;

// Check for errors
if (shaderModule == nullptr) {
std::cerr << "Could not load shader!" << std::endl;
exit(1);
}

// Create the render pipeline
RenderPipelineDescriptor pipelineDesc;
Expand Down Expand Up @@ -433,15 +386,13 @@ RequiredLimits Application::GetRequiredLimits(Adapter adapter) const {

// We use at most 2 vertex attributes
requiredLimits.limits.maxVertexAttributes = 2;
// ^ This was 1
// We should also tell that we use 1 vertex buffers
requiredLimits.limits.maxVertexBuffers = 1;
// Maximum size of a buffer is 6 vertices of 5 float each
requiredLimits.limits.maxBufferSize = 6 * 5 * sizeof(float);
// ^ This was a 2
// Maximum size of a buffer is 15 vertices of 5 float each
requiredLimits.limits.maxBufferSize = 15 * 5 * sizeof(float);
// ^^ This was a 6
// Maximum stride between 2 consecutive vertices in the vertex buffer
requiredLimits.limits.maxVertexBufferArrayStride = 5 * sizeof(float);
// ^ This was a 2

// There is a maximum of 3 float forwarded from vertex to fragment shader
requiredLimits.limits.maxInterStageShaderComponents = 3;
Expand All @@ -451,28 +402,24 @@ RequiredLimits Application::GetRequiredLimits(Adapter adapter) const {
// limits.
requiredLimits.limits.minUniformBufferOffsetAlignment = supportedLimits.limits.minUniformBufferOffsetAlignment;
requiredLimits.limits.minStorageBufferOffsetAlignment = supportedLimits.limits.minStorageBufferOffsetAlignment;

return requiredLimits;
}

void Application::InitializeBuffers() {
// Define point data
// The de-duplicated list of point positions
std::vector<float> pointData = {
// x, y, r, g, b
-0.5, -0.5, 1.0, 0.0, 0.0, // Point #0
+0.5, -0.5, 0.0, 1.0, 0.0, // Point #1
+0.5, +0.5, 0.0, 0.0, 1.0, // Point #2
-0.5, +0.5, 1.0, 1.0, 0.0 // Point #3
};
// Define data vectors, but without filling them in
std::vector<float> pointData;
std::vector<uint16_t> indexData;

// Define index data
// This is a list of indices referencing positions in the pointData
std::vector<uint16_t> indexData = {
0, 1, 2, // Triangle #0 connects points #0, #1 and #2
0, 2, 3 // Triangle #1 connects points #0, #2 and #3
};
// Here we use the new 'loadGeometry' function:
bool success = ResourceManager::loadGeometry(RESOURCE_DIR "/webgpu.txt", pointData, indexData);

// Check for errors
if (!success) {
std::cerr << "Could not load geometry!" << std::endl;
exit(1);
}

// We now store the index count rather than the vertex count
indexCount = static_cast<uint32_t>(indexData.size());

// Create vertex buffer
Expand Down
39 changes: 39 additions & 0 deletions resources/shader.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* A structure with fields labeled with vertex attribute locations can be used
* as input to the entry point of a shader.
*/
struct VertexInput {
@location(0) position: vec2f,
@location(1) color: vec3f,
};

/**
* A structure with fields labeled with builtins and locations can also be used
* as *output* of the vertex shader, which is also the input of the fragment
* shader.
*/
struct VertexOutput {
@builtin(position) position: vec4f,
// The location here does not refer to a vertex attribute, it just means
// that this field must be handled by the rasterizer.
// (It can also refer to another field of another struct that would be used
// as input to the fragment shader.)
@location(0) color: vec3f,
};

@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
// ^^^^^^^^^^^^ We return a custom struct
var out: VertexOutput; // create the output struct
let ratio = 640.0 / 480.0; // The width and height of the target surface
let offset = vec2f(-0.6875, -0.463); // The offset that we want to apply to the position
out.position = vec4f(in.position.x + offset.x, (in.position.y + offset.y) * ratio, 0.0, 1.0);
out.color = in.color; // forward the color attribute to the fragment shader
return out;
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4f {
// ^^^^^^^^^^^^^^^^ Use for instance the same struct as what the vertex outputs
return vec4f(in.color, 1.0); // use the interpolated color coming from the vertex shader
}
29 changes: 29 additions & 0 deletions resources/webgpu.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[points]
# x y r g b

0.5 0.0 0.0 0.353 0.612
1.0 0.866 0.0 0.353 0.612
0.0 0.866 0.0 0.353 0.612

0.75 0.433 0.0 0.4 0.7
1.25 0.433 0.0 0.4 0.7
1.0 0.866 0.0 0.4 0.7

1.0 0.0 0.0 0.463 0.8
1.25 0.433 0.0 0.463 0.8
0.75 0.433 0.0 0.463 0.8

1.25 0.433 0.0 0.525 0.91
1.375 0.65 0.0 0.525 0.91
1.125 0.65 0.0 0.525 0.91

1.125 0.65 0.0 0.576 1.0
1.375 0.65 0.0 0.576 1.0
1.25 0.866 0.0 0.576 1.0

[indices]
0 1 2
3 4 5
6 7 8
9 10 11
12 13 14

0 comments on commit 1a07c85

Please sign in to comment.