Skip to content

Commit

Permalink
Step 037-vanilla
Browse files Browse the repository at this point in the history
  • Loading branch information
eliemichel committed Oct 5, 2024
1 parent 69159b1 commit 4ce1b23
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 77 deletions.
21 changes: 21 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,33 @@ add_executable(App
main.cpp
webgpu-utils.h
webgpu-utils.cpp
ResourceManager.h
ResourceManager.cpp
)

target_link_libraries(App PRIVATE glfw webgpu glfw3webgpu)

target_copy_webgpu_binaries(App)

# 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()

set_target_properties(App PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
Expand Down
91 changes: 91 additions & 0 deletions ResourceManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#include "ResourceManager.h"

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

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;
}

WGPUShaderModule ResourceManager::loadShaderModule(const std::filesystem::path& path, WGPUDevice 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);

WGPUShaderModuleWGSLDescriptor shaderCodeDesc{};
shaderCodeDesc.chain.next = nullptr;
shaderCodeDesc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor;
shaderCodeDesc.code = shaderSource.c_str();

WGPUShaderModuleDescriptor shaderDesc{};
shaderDesc.nextInChain = nullptr;
#ifdef WEBGPU_BACKEND_WGPU
shaderDesc.hintCount = 0;
shaderDesc.hints = nullptr;
#endif
shaderDesc.nextInChain = &shaderCodeDesc.chain;
return wgpuDeviceCreateShaderModule(device, &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.h>

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 WGPUShaderModule loadShaderModule(
const std::filesystem::path& path,
WGPUDevice device
);
};
100 changes: 23 additions & 77 deletions main.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "webgpu-utils.h"
#include "ResourceManager.h"

#include <webgpu/webgpu.h>
#ifdef WEBGPU_BACKEND_WGPU
Expand All @@ -16,48 +17,6 @@
#include <cassert>
#include <vector>

// 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,
};
@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
}
)";

class Application {
public:
// Initialize everything and return true if it went all right
Expand Down Expand Up @@ -308,22 +267,15 @@ WGPUTextureView Application::GetNextSurfaceTextureView() {
}

void Application::InitializePipeline() {
// Load the shader module
WGPUShaderModuleDescriptor 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
WGPUShaderModuleWGSLDescriptor shaderCodeDesc{};
// Set the chained struct's header
shaderCodeDesc.chain.next = nullptr;
shaderCodeDesc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor;
// Connect the chain
shaderDesc.nextInChain = &shaderCodeDesc.chain;
shaderCodeDesc.code = shaderSource;
WGPUShaderModule shaderModule = wgpuDeviceCreateShaderModule(device, &shaderDesc);
std::cout << "Creating shader module..." << std::endl;
WGPUShaderModule 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
WGPURenderPipelineDescriptor pipelineDesc{};
Expand Down Expand Up @@ -475,15 +427,13 @@ WGPURequiredLimits Application::GetRequiredLimits(WGPUAdapter 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
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 @@ -498,22 +448,18 @@ WGPURequiredLimits Application::GetRequiredLimits(WGPUAdapter adapter) const {
}

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());
Expand Down
42 changes: 42 additions & 0 deletions resources/shader.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* 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 {
// We apply a gamma-correction to the color
// We need to convert our input sRGB color into linear before the target
// surface converts it back to sRGB.
let linear_color = pow(in.color, vec3f(2.2));
return vec4f(linear_color, 1.0);
}
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 4ce1b23

Please sign in to comment.