This commit is contained in:
Vicente Ferrari Smith 2026-04-28 19:46:41 +02:00
parent a0b7e4c0d8
commit 6883d103ff
36 changed files with 2752 additions and 1444 deletions

BIN
.DS_Store vendored

Binary file not shown.

1
.idea/misc.xml generated
View File

@ -4,4 +4,5 @@
<option name="pythonIntegrationState" value="YES" />
</component>
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
<component name="WestSettings"><![CDATA[{}]]></component>
</project>

View File

@ -5,38 +5,170 @@ set(CMAKE_CXX_STANDARD 23)
include(FetchContent)
find_package(Vulkan REQUIRED COMPONENTS volk)
add_executable(v main.cpp
misc.cpp
misc.h
renderer/graphics.cpp
renderer/graphics.h
renderer/texture.cpp
renderer/texture.h
renderer/texture_sheet.cpp
renderer/texture_sheet.h
renderer/sprite.cpp
renderer/sprite.h
renderer/graphics_private.h
)
find_library(SLANG_LIB NAMES slang HINTS "$ENV{VULKAN_SDK}/lib")
find_path(SLANG_INCLUDE_DIR NAMES slang/slang.h HINTS "$ENV{VULKAN_SDK}/include")
find_file(SLANG_DLL NAMES slang.dll HINTS "$ENV{VULKAN_SDK}/bin")
target_include_directories(v
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
if(SLANG_LIB AND SLANG_INCLUDE_DIR)
add_library(slang_sdk SHARED IMPORTED)
set_target_properties(slang_sdk PROPERTIES
IMPORTED_IMPLIB "${SLANG_LIB}"
INTERFACE_INCLUDE_DIRECTORIES "${SLANG_INCLUDE_DIR}"
target_link_libraries(v PRIVATE glfw glm::glm stb)
if (EMSCRIPTEN)
set(CMAKE_VERBOSE_MAKEFILE ON)
message(STATUS "Configuring for Emscripten...")
# 1. Find all .slang files in the shaders directory
file(GLOB SLANG_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.slang")
set(WGSL_OUTPUTS "")
# 2. Iterate through the found files
foreach(SOURCE_PATH ${SLANG_SOURCES})
# Get the filename without the path or extension (e.g., "triangle")
get_filename_component(FILENAME_WE ${SOURCE_PATH} NAME_WE)
# Define the output path in the build directory
set(OUTPUT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/shaders/${FILENAME_WE}.wgsl")
# 3. Define the compilation command
add_custom_command(
OUTPUT ${OUTPUT_PATH}
COMMAND slangc ${SOURCE_PATH} -target wgsl -o ${OUTPUT_PATH}
DEPENDS ${SOURCE_PATH}
COMMENT "Compiling Slang: ${FILENAME_WE}.slang -> ${FILENAME_WE}.wgsl"
VERBATIM
)
# If we found the DLL, set it as the location;
# otherwise, on non-Windows systems, SLANG_LIB is the location.
if(WIN32 AND SLANG_DLL)
set_target_properties(slang_sdk PROPERTIES IMPORTED_LOCATION "${SLANG_DLL}")
else()
set_target_properties(slang_sdk PROPERTIES IMPORTED_LOCATION "${SLANG_LIB}")
endif()
list(APPEND WGSL_OUTPUTS ${OUTPUT_PATH})
endforeach()
message(STATUS "Slang found via VULKAN_SDK: ${SLANG_LIB}")
else()
message(FATAL_ERROR "VULKAN_SDK env var is set, but Slang wasn't found inside it!")
endif()
# 4. Create a single target that tracks all these outputs
add_custom_target(CompileShaders ALL DEPENDS ${WGSL_OUTPUTS})
FetchContent_Declare(
glfw
GIT_REPOSITORY https://github.com/glfw/glfw.git
GIT_TAG 232164f62b0edbf667cba37c91bab92ffbb020d0
)
FetchContent_MakeAvailable(glfw)
add_dependencies(v CompileShaders)
set_target_properties(v PROPERTIES SUFFIX ".html")
target_compile_options(v PRIVATE
"--use-port=emdawnwebgpu"
"--use-port=contrib.glfw3"
)
target_link_options(v PRIVATE
"--use-port=emdawnwebgpu"
"-sASYNCIFY=1"
"--use-port=contrib.glfw3"
"--preload-file" "${CMAKE_CURRENT_SOURCE_DIR}/shaders"
)
target_sources(v PRIVATE
renderer/webgpu/renderer.cpp
renderer/webgpu/renderer.h
renderer/webgpu/webgpu.cpp
renderer/webgpu/webgpu.h
renderer/webgpu/utils_emscripten.cpp
)
elseif (WIN32)
message(STATUS "Configuring for Windows...")
find_package(slang REQUIRED)
find_package(Vulkan REQUIRED COMPONENTS volk)
find_package(glfw3 REQUIRED)
target_link_libraries(v PRIVATE Vulkan::Vulkan slang::slang)
target_sources(v PRIVATE
renderer/vulkan/vulkan.cpp
renderer/vulkan/vulkan.h
renderer/vulkan/renderer.cpp
renderer/vulkan/renderer.h
)
elseif (APPLE)
message(STATUS "Configuring for macOS...")
find_package(slang REQUIRED)
find_package(glfw3 REQUIRED)
target_include_directories(v PUBLIC
"~/Dev/libraries/metal-cpp"
"~/Dev/libraries/metal-cpp-extensions"
)
target_link_libraries(v PRIVATE
"-framework Metal"
"-framework MetalKit"
"-framework AppKit"
"-framework Foundation"
"-framework QuartzCore"
slang::slang
)
target_sources(v PRIVATE
renderer/metal/metal.cpp
renderer/metal/metal.h
renderer/metal/renderer.cpp
renderer/metal/renderer.h
)
#[[ #shaders
set(SHADER_DIR ${CMAKE_SOURCE_DIR}/shaders)
set(SHADER_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/renderer/metal)
set(SHADER_OUTPUT_DIR ${CMAKE_SOURCE_DIR}/shaders)
file(GLOB SHADER_SOURCES
${SHADER_DIR}/*.metal)
set(AIR_FILES)
foreach(SHADER ${SHADER_SOURCES})
get_filename_component(NAME ${SHADER} NAME_WE)
set(AIR_FILE ${SHADER_OUTPUT_DIR}/${NAME}.air)
add_custom_command(
OUTPUT ${AIR_FILE}
COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADER_OUTPUT_DIR}
COMMAND xcrun -sdk macosx metal -frecord-sources -gline-tables-only
-I ${SHADER_INCLUDE_DIR}
-c ${SHADER}
-o ${AIR_FILE}
DEPENDS ${SHADER}
COMMENT "Compiling ${NAME}.metal"
)
list(APPEND AIR_FILES ${AIR_FILE})
endforeach()
set(METALLIB ${SHADER_OUTPUT_DIR}/shaders.metallib)
add_custom_command(
OUTPUT ${METALLIB}
COMMAND xcrun -sdk macosx metallib
${AIR_FILES}
-o ${METALLIB}
DEPENDS ${AIR_FILES}
COMMENT "Linking shaders.metallib"
)
add_custom_target(shaders ALL
DEPENDS ${METALLIB}
)
add_dependencies(v shaders)]]
endif ()
FetchContent_Declare(
glm
@ -56,27 +188,6 @@ FetchContent_MakeAvailable(stb)
add_library(stb INTERFACE)
target_include_directories(stb INTERFACE ${stb_SOURCE_DIR})
add_executable(v main.cpp
renderer/init.cpp
renderer/init.h
renderer/renderer.cpp
renderer/renderer.h
misc.cpp
misc.h
renderer/sprite.cpp
renderer/sprite.h
renderer/texture.cpp
renderer/texture.h
renderer/texture_sheet.cpp
renderer/texture_sheet.h)
target_include_directories(v
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(v PRIVATE glfw Vulkan::Vulkan glm::glm stb slang_sdk)
set(SHADER_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/shaders")
set(SHADER_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders")
file(GLOB_RECURSE SHADER_SOURCES
@ -85,7 +196,7 @@ file(GLOB_RECURSE SHADER_SOURCES
"${SHADER_SOURCE_DIR}/*.comp"
)
set(SPIRV_BINARY_FILES "")
#[[set(SPIRV_BINARY_FILES "")
foreach(SHADER ${SHADER_SOURCES})
@ -104,4 +215,4 @@ endforeach()
add_custom_target(compile_shaders ALL DEPENDS ${SPIRV_BINARY_FILES})
add_dependencies(v compile_shaders)
add_dependencies(v compile_shaders)]]

View File

@ -2,16 +2,17 @@
#include <vector>
#include <fstream>
#include <cassert>
#include <cstdint>
#define VOLK_IMPLEMENTATION
#include <Volk/volk.h>
#define VMA_IMPLEMENTATION
#include <vma/vk_mem_alloc.h>
#ifdef __EMSCRIPTEN__
#include <GLFW/emscripten_glfw3.h>
#include <emscripten.h>
#else
#include <GLFW/glfw3.h>
#endif
#include "renderer/init.h"
#include "renderer/renderer.h"
#include "renderer/texture.h"
#include "renderer/graphics.h"
#include "renderer/texture_sheet.h"
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
@ -24,14 +25,25 @@ int32_t window_height = 480;
uint64_t t = 0;
uint64_t accumulator = 0;
uint64_t dt = 0;
uint64_t dt = glfwGetTimerFrequency() / 60; // 1/60 s
int main() {
uint64_t current_time = glfwGetTimerValue();
sprite_t sprite {
.origin = {0.5, 0.5},
.scale = {512, 512},
.rotation = 0.0,
.colour = {1.0, 1.0, 1.0, 1.0},
.alpha = 1.0,
.window_space = false,
.maintain_ar = false,
.texture = "assets/boy.png"
};
int init() {
if (!glfwInit())
return -1;
dt = (uint64_t) (1.0 / 60.0 * glfwGetTimerFrequency());
std::println("Hello, Sailor!");
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
@ -41,22 +53,18 @@ int main() {
return -1;
}
createInstance(window);
createSurface(window);
createDevice();
graphics_init(window);
createSwapchain(window);
texture_manager.load("assets/boy.png");
slang::createGlobalSession(slangGlobalSession.writeRef());
return 0;
}
Renderer renderer(window);
texture_manager.load("assets/boy.png", renderer);
uint64_t current_time = glfwGetTimerValue();
while (!glfwWindowShouldClose(window)) {
bool is_running() {
return !glfwWindowShouldClose(window);
}
void main_loop(void *data) {
uint64_t new_time = glfwGetTimerValue();
uint64_t frame_time = new_time - current_time;
current_time = new_time;
@ -74,31 +82,38 @@ int main() {
}
// std::println("Updates: {}", updates);
// std::println("frame time: {}", ((double) (frame_time) / (double) glfwGetTimerFrequency()));
// std::println("fps: {}", 1.0 / ((double) (frame_time) / (double) glfwGetTimerFrequency()));
renderer.begin_frame();
begin_frame();
double f = 15.0 * (t / (double) glfwGetTimerFrequency());
renderer.submit_quad(
{
100.0 + 10.0 * glm::sin(f),
100.0 - 10.0 * glm::cos(f),
},
{100.0, 100.0});
renderer.submit_quad({400.0, 400.0}, {20.0, 20.0});
// renderer.submit_sprite();
renderer.end_frame();
double f = (t / (double) glfwGetTimerFrequency());
std::println("{}", f);
for (int x = sprite.scale.x / 2; x < window_width; x += sprite.scale.x) {
submit_sprite({x + 30 * cos(f), 200 + 30 * sin(f)}, sprite);
}
vkDeviceWaitIdle(device);
end_frame(window);
}
void deinit() {
graphics_deinit();
glfwDestroyWindow(window);
glfwTerminate();
}
int main() {
init();
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop_arg(main_loop, nullptr, 0, true);
#else
while (is_running()) {
main_loop(nullptr);
}
#endif
deinit();
return 0;
}

View File

View File

@ -1,3 +0,0 @@
//
// Created by Vicente Ferrari Smith on 26.02.26.
//

View File

@ -5,15 +5,17 @@
#include "misc.h"
#include <fstream>
std::vector<char> loadFile(const char* path) {
std::string read_entire_file(const std::string &path) {
std::ifstream f(path, std::ios::binary | std::ios::ate);
if (f.good()) {
const uint32_t size = f.tellg();
std::vector<char> data(size);
f.seekg(0);
f.read(data.data(), size);
return data;
}
return {};
if (!f.is_open()) return {};
const std::streamsize size = f.tellg();
std::string buffer;
buffer.resize(static_cast<size_t>(size)); // Pre-allocate the string memory
f.seekg(0);
f.read(&buffer[0], size); // Read directly into the string buffer
return buffer;
}

2
misc.h
View File

@ -7,7 +7,7 @@
#include <vector>
std::vector<char> loadFile(const char* path);
std::string read_entire_file(const std::string &path);
template<typename T, typename F>

View File

@ -1,3 +1,14 @@
//
// Created by Vicente Ferrari Smith on 02.03.26.
//
#include "graphics.h"
#include "graphics_private.h"
void graphics_init(GLFWwindow *window) {
#ifndef __EMSCRIPTEN__
slang::createGlobalSession(slangGlobalSession.writeRef());
#endif
platform_graphics_init(window);
}

View File

@ -2,13 +2,47 @@
// Created by Vicente Ferrari Smith on 26.02.26.
//
#ifndef V_RENDERER_H
#define V_RENDERER_H
#ifndef V_GRAPHICS_H
#define V_GRAPHICS_H
struct Graphics {
Graphics();
};
#ifdef __EMSCRIPTEN__
#include "webgpu/renderer.h"
#elifdef __APPLE__
#include "metal/renderer.h"
#elifdef _WIN32
#include "vulkan/init.h"
#include "vulkan/renderer.h"
#endif
#ifdef __EMSCRIPTEN__
#include <GLFW/emscripten_glfw3.h>
#else
#include <GLFW/glfw3.h>
#endif
#include <glm/glm.hpp>
#endif //V_RENDERER_H
#ifndef __EMSCRIPTEN__
#include <slang.h>
#include <slang-com-ptr.h>
inline Slang::ComPtr<slang::IGlobalSession> slangGlobalSession;
inline Slang::ComPtr<slang::ISession> slangSession;
#endif
#include "sprite.h"
void graphics_init(GLFWwindow *window);
void graphics_deinit();
void begin_frame();
void end_frame(GLFWwindow *window);
void submit_quad();
void submit_sprite(glm::vec2 pos, const sprite_t &sprite);
void upload_texture(
int w,
int h,
const void* pixels,
Texture *texture);
#endif //V_GRAPHICS_H

View File

@ -5,4 +5,12 @@
#ifndef V_GRAPHICS_PRIVATE_H
#define V_GRAPHICS_PRIVATE_H
#ifdef __EMSCRIPTEN__
#include <GLFW/emscripten_glfw3.h>
#else
#include <GLFW/glfw3.h>
#endif
void platform_graphics_init(GLFWwindow *window);
#endif //V_GRAPHICS_PRIVATE_H

View File

@ -1,3 +1,637 @@
//
// Created by Vicente Ferrari Smith on 27.02.26.
//
/*
See LICENSE folder for this sampleâs licensing information.
Abstract:
Implementation of vector, matrix, and quaternion math utility functions useful for 3D graphics
rendering with Metal
Metal uses column-major matrices and column-vector inputs.
linearIndex cr example with reference elements
0 4 8 12 00 10 20 30 sx 10 20 tx
1 5 9 13 --> 01 11 21 31 --> 01 sy 21 ty
2 6 10 14 02 12 22 32 02 12 sz tz
3 7 11 15 03 13 23 33 03 13 1/d 33
The "cr" names are for <column><row>
*/
#include "AAPLMathUtilities.h"
#include <assert.h>
#include <stdlib.h>
uint32_t seed_lo, seed_hi;
static float inline F16ToF32(const __fp16 *address) {
return *address;
}
float AAPL_SIMD_OVERLOAD float32_from_float16(uint16_t i) {
return F16ToF32((__fp16 *)&i);
}
static inline void F32ToF16(float F32, __fp16 *F16Ptr) {
*F16Ptr = F32;
}
uint16_t AAPL_SIMD_OVERLOAD float16_from_float32(float f) {
uint16_t f16;
F32ToF16(f, (__fp16 *)&f16);
return f16;
}
vector_float3 AAPL_SIMD_OVERLOAD generate_random_vector(float min, float max)
{
vector_float3 rand;
float range = max - min;
rand.x = ((double)random() / (double) (0x7FFFFFFF)) * range + min;
rand.y = ((double)random() / (double) (0x7FFFFFFF)) * range + min;
rand.z = ((double)random() / (double) (0x7FFFFFFF)) * range + min;
return rand;
}
void AAPL_SIMD_OVERLOAD seedRand(uint32_t seed) {
seed_lo = seed; seed_hi = ~seed;
}
int32_t AAPL_SIMD_OVERLOAD randi(void) {
seed_hi = (seed_hi<<16) + (seed_hi>>16);
seed_hi += seed_lo; seed_lo += seed_hi;
return seed_hi;
}
float AAPL_SIMD_OVERLOAD randf(float x) {
return (x * randi() / (float)0x7FFFFFFF);
}
float AAPL_SIMD_OVERLOAD degrees_from_radians(float radians) {
return (radians / M_PI) * 180;
}
float AAPL_SIMD_OVERLOAD radians_from_degrees(float degrees) {
return (degrees / 180) * M_PI;
}
static vector_float3 AAPL_SIMD_OVERLOAD vector_make(float x, float y, float z) {
return (vector_float3){ x, y, z };
}
vector_float3 AAPL_SIMD_OVERLOAD vector_lerp(vector_float3 v0, vector_float3 v1, float t) {
return ((1 - t) * v0) + (t * v1);
}
vector_float4 AAPL_SIMD_OVERLOAD vector_lerp(vector_float4 v0, vector_float4 v1, float t) {
return ((1 - t) * v0) + (t * v1);
}
//------------------------------------------------------------------------------
// matrix_make_rows takes input data with rows of elements.
// This way, the calling code matrix data can look like the rows
// of a matrix made for transforming column vectors.
// Indices are m<column><row>
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix_make_rows(
float m00, float m10, float m20,
float m01, float m11, float m21,
float m02, float m12, float m22) {
return (matrix_float3x3){ {
{ m00, m01, m02 }, // each line here provides column data
{ m10, m11, m12 },
{ m20, m21, m22 } } };
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_make_rows(
float m00, float m10, float m20, float m30,
float m01, float m11, float m21, float m31,
float m02, float m12, float m22, float m32,
float m03, float m13, float m23, float m33) {
return (matrix_float4x4){ {
{ m00, m01, m02, m03 }, // each line here provides column data
{ m10, m11, m12, m13 },
{ m20, m21, m22, m23 },
{ m30, m31, m32, m33 } } };
}
// each arg is a column vector
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix_make_columns(
vector_float3 col0,
vector_float3 col1,
vector_float3 col2) {
return (matrix_float3x3){ col0, col1, col2 };
}
// each arg is a column vector
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_make_columns(
vector_float4 col0,
vector_float4 col1,
vector_float4 col2,
vector_float4 col3) {
return (matrix_float4x4){ col0, col1, col2, col3 };
}
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix3x3_from_quaternion(quaternion_float q) {
float xx = q.x * q.x;
float xy = q.x * q.y;
float xz = q.x * q.z;
float xw = q.x * q.w;
float yy = q.y * q.y;
float yz = q.y * q.z;
float yw = q.y * q.w;
float zz = q.z * q.z;
float zw = q.z * q.w;
// indices are m<column><row>
float m00 = 1 - 2 * (yy + zz);
float m10 = 2 * (xy - zw);
float m20 = 2 * (xz + yw);
float m01 = 2 * (xy + zw);
float m11 = 1 - 2 * (xx + zz);
float m21 = 2 * (yz - xw);
float m02 = 2 * (xz - yw);
float m12 = 2 * (yz + xw);
float m22 = 1 - 2 * (xx + yy);
return matrix_make_rows(m00, m10, m20,
m01, m11, m21,
m02, m12, m22);
}
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix3x3_rotation(float radians, vector_float3 axis) {
axis = vector_normalize(axis);
float ct = cosf(radians);
float st = sinf(radians);
float ci = 1 - ct;
float x = axis.x, y = axis.y, z = axis.z;
return matrix_make_rows( ct + x * x * ci, x * y * ci - z * st, x * z * ci + y * st,
y * x * ci + z * st, ct + y * y * ci, y * z * ci - x * st,
z * x * ci - y * st, z * y * ci + x * st, ct + z * z * ci );
}
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix3x3_rotation(float radians, float x, float y, float z) {
return matrix3x3_rotation(radians, vector_make(x, y, z));
}
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix3x3_scale(float sx, float sy, float sz) {
return matrix_make_rows(sx, 0, 0,
0, sy, 0,
0, 0, sz);
}
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix3x3_scale(vector_float3 s) {
return matrix_make_rows(s.x, 0, 0,
0, s.y, 0,
0, 0, s.z);
}
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix3x3_upper_left(matrix_float4x4 m) {
vector_float3 x = m.columns[0].xyz;
vector_float3 y = m.columns[1].xyz;
vector_float3 z = m.columns[2].xyz;
return matrix_make_columns(x, y, z);
}
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix_inverse_transpose(matrix_float3x3 m) {
return matrix_invert(matrix_transpose(m));
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_from_quaternion(quaternion_float q) {
float xx = q.x * q.x;
float xy = q.x * q.y;
float xz = q.x * q.z;
float xw = q.x * q.w;
float yy = q.y * q.y;
float yz = q.y * q.z;
float yw = q.y * q.w;
float zz = q.z * q.z;
float zw = q.z * q.w;
// indices are m<column><row>
float m00 = 1 - 2 * (yy + zz);
float m10 = 2 * (xy - zw);
float m20 = 2 * (xz + yw);
float m01 = 2 * (xy + zw);
float m11 = 1 - 2 * (xx + zz);
float m21 = 2 * (yz - xw);
float m02 = 2 * (xz - yw);
float m12 = 2 * (yz + xw);
float m22 = 1 - 2 * (xx + yy);
matrix_float4x4 matrix = matrix_make_rows(m00, m10, m20, 0,
m01, m11, m21, 0,
m02, m12, m22, 0,
0, 0, 0, 1);
return matrix;
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_rotation(float radians, vector_float3 axis) {
axis = vector_normalize(axis);
float ct = cosf(radians);
float st = sinf(radians);
float ci = 1 - ct;
float x = axis.x, y = axis.y, z = axis.z;
return matrix_make_rows(
ct + x * x * ci, x * y * ci - z * st, x * z * ci + y * st, 0,
y * x * ci + z * st, ct + y * y * ci, y * z * ci - x * st, 0,
z * x * ci - y * st, z * y * ci + x * st, ct + z * z * ci, 0,
0, 0, 0, 1);
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_rotation(float radians, float x, float y, float z) {
return matrix4x4_rotation(radians, vector_make(x, y, z));
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_identity(void) {
return matrix_make_rows(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1 );
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_scale(float sx, float sy, float sz) {
return matrix_make_rows(sx, 0, 0, 0,
0, sy, 0, 0,
0, 0, sz, 0,
0, 0, 0, 1 );
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_scale(vector_float3 s) {
return matrix_make_rows(s.x, 0, 0, 0,
0, s.y, 0, 0,
0, 0, s.z, 0,
0, 0, 0, 1 );
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_translation(float tx, float ty, float tz) {
return matrix_make_rows(1, 0, 0, tx,
0, 1, 0, ty,
0, 0, 1, tz,
0, 0, 0, 1 );
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_translation(vector_float3 t) {
return matrix_make_rows(1, 0, 0, t.x,
0, 1, 0, t.y,
0, 0, 1, t.z,
0, 0, 0, 1 );
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_scale_translation(vector_float3 s, vector_float3 t) {
return matrix_make_rows(s.x, 0, 0, t.x,
0, s.y, 0, t.y,
0, 0, s.z, t.z,
0, 0, 0, 1 );
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_look_at_left_hand(vector_float3 eye,
vector_float3 target,
vector_float3 up) {
vector_float3 z = vector_normalize(target - eye);
vector_float3 x = vector_normalize(vector_cross(up, z));
vector_float3 y = vector_cross(z, x);
vector_float3 t = vector_make(-vector_dot(x, eye), -vector_dot(y, eye), -vector_dot(z, eye));
return matrix_make_rows(x.x, x.y, x.z, t.x,
y.x, y.y, y.z, t.y,
z.x, z.y, z.z, t.z,
0, 0, 0, 1 );
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_look_at_left_hand(float eyeX, float eyeY, float eyeZ,
float centerX, float centerY, float centerZ,
float upX, float upY, float upZ) {
vector_float3 eye = vector_make(eyeX, eyeY, eyeZ);
vector_float3 center = vector_make(centerX, centerY, centerZ);
vector_float3 up = vector_make(upX, upY, upZ);
return matrix_look_at_left_hand(eye, center, up);
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_look_at_right_hand(vector_float3 eye,
vector_float3 target,
vector_float3 up) {
vector_float3 z = vector_normalize(eye - target);
vector_float3 x = vector_normalize(vector_cross(up, z));
vector_float3 y = vector_cross(z, x);
vector_float3 t = vector_make(-vector_dot(x, eye), -vector_dot(y, eye), -vector_dot(z, eye));
return matrix_make_rows(x.x, x.y, x.z, t.x,
y.x, y.y, y.z, t.y,
z.x, z.y, z.z, t.z,
0, 0, 0, 1 );
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_look_at_right_hand(float eyeX, float eyeY, float eyeZ,
float centerX, float centerY, float centerZ,
float upX, float upY, float upZ) {
vector_float3 eye = vector_make(eyeX, eyeY, eyeZ);
vector_float3 center = vector_make(centerX, centerY, centerZ);
vector_float3 up = vector_make(upX, upY, upZ);
return matrix_look_at_right_hand(eye, center, up);
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_ortho_left_hand(float left, float right, float bottom, float top, float nearZ, float farZ) {
return matrix_make_rows(
2 / (right - left), 0, 0, (left + right) / (left - right),
0, 2 / (top - bottom), 0, (top + bottom) / (bottom - top),
0, 0, 1 / (farZ - nearZ), nearZ / (nearZ - farZ),
0, 0, 0, 1 );
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_ortho_right_hand(float left, float right, float bottom, float top, float nearZ, float farZ) {
return matrix_make_rows(
2 / (right - left), 0, 0, (left + right) / (left - right),
0, 2 / (top - bottom), 0, (top + bottom) / (bottom - top),
0, 0, -1 / (farZ - nearZ), nearZ / (nearZ - farZ),
0, 0, 0, 1 );
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_perspective_left_hand(float fovyRadians, float aspect, float nearZ, float farZ) {
float ys = 1 / tanf(fovyRadians * 0.5);
float xs = ys / aspect;
float zs = farZ / (farZ - nearZ);
return matrix_make_rows(xs, 0, 0, 0,
0, ys, 0, 0,
0, 0, zs, -nearZ * zs,
0, 0, 1, 0 );
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_perspective_right_hand(float fovyRadians, float aspect, float nearZ, float farZ) {
float ys = 1 / tanf(fovyRadians * 0.5);
float xs = ys / aspect;
float zs = farZ / (nearZ - farZ);
return matrix_make_rows(xs, 0, 0, 0,
0, ys, 0, 0,
0, 0, zs, nearZ * zs,
0, 0, -1, 0 );
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_perspective_frustum_right_hand(float l, float r, float b, float t, float n, float f) {
return matrix_make_rows(
2 * n / (r - l), 0, (r + l) / (r - l), 0,
0, 2 * n / (t - b), (t + b) / (t - b), 0,
0, 0, -f / (f - n), -f * n / (f - n),
0, 0, -1, 0 );
}
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_inverse_transpose(matrix_float4x4 m) {
return matrix_invert(matrix_transpose(m));
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion(float x, float y, float z, float w) {
return (quaternion_float){ x, y, z, w };
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion(vector_float3 v, float w) {
return (quaternion_float){ v.x, v.y, v.z, w };
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion_identity() {
return quaternion(0, 0, 0, 1);
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion_from_axis_angle(vector_float3 axis, float radians) {
float t = radians * 0.5;
return quaternion(axis.x * sinf(t), axis.y * sinf(t), axis.z * sinf(t), cosf(t));
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion_from_euler(vector_float3 euler) {
quaternion_float q;
float cx = cosf(euler.x / 2.f);
float cy = cosf(euler.y / 2.f);
float cz = cosf(euler.z / 2.f);
float sx = sinf(euler.x / 2.f);
float sy = sinf(euler.y / 2.f);
float sz = sinf(euler.z / 2.f);
q.w = cx * cy * cz + sx * sy * sz;
q.x = sx * cy * cz - cx * sy * sz;
q.y = cx * sy * cz + sx * cy * sz;
q.z = cx * cy * sz - sx * sy * cz;
return q;
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion(matrix_float3x3 m) {
float m00 = m.columns[0].x;
float m11 = m.columns[1].y;
float m22 = m.columns[2].z;
float x = sqrtf(1 + m00 - m11 - m22) * 0.5;
float y = sqrtf(1 - m00 + m11 - m22) * 0.5;
float z = sqrtf(1 - m00 - m11 + m22) * 0.5;
float w = sqrtf(1 + m00 + m11 + m22) * 0.5;
return quaternion(x, y, z, w);
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion(matrix_float4x4 m) {
return quaternion(matrix3x3_upper_left(m));
}
float AAPL_SIMD_OVERLOAD quaternion_length(quaternion_float q) {
// return sqrt(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w);
return vector_length(q);
}
float AAPL_SIMD_OVERLOAD quaternion_length_squared(quaternion_float q) {
// return q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w;
return vector_length_squared(q);
}
vector_float3 AAPL_SIMD_OVERLOAD quaternion_axis(quaternion_float q) {
// This query doesn't make sense if w > 1, but we do our best by
// forcing q to be a unit quaternion if it obviously isn't
if (q.w > 1.0)
{
q = quaternion_normalize(q);
}
float axisLen = sqrtf(1 - q.w * q.w);
if (axisLen < 1e-5)
{
// At lengths this small, direction is arbitrary
return vector_make(1, 0, 0);
}
else
{
return vector_make(q.x / axisLen, q.y / axisLen, q.z / axisLen);
}
}
float AAPL_SIMD_OVERLOAD quaternion_angle(quaternion_float q) {
return 2 * acosf(q.w);
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion_normalize(quaternion_float q) {
// return q / quaternion_length(q);
return vector_normalize(q);
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion_inverse(quaternion_float q) {
return quaternion_conjugate(q) / quaternion_length_squared(q);
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion_conjugate(quaternion_float q) {
return quaternion(-q.x, -q.y, -q.z, q.w);
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion_multiply(quaternion_float q0, quaternion_float q1) {
quaternion_float q;
q.x = q0.w*q1.x + q0.x*q1.w + q0.y*q1.z - q0.z*q1.y;
q.y = q0.w*q1.y - q0.x*q1.z + q0.y*q1.w + q0.z*q1.x;
q.z = q0.w*q1.z + q0.x*q1.y - q0.y*q1.x + q0.z*q1.w;
q.w = q0.w*q1.w - q0.x*q1.x - q0.y*q1.y - q0.z*q1.z;
return q;
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion_slerp(quaternion_float q0, quaternion_float q1, float t) {
quaternion_float q;
float cosHalfTheta = vector_dot(q0, q1);
if (fabs(cosHalfTheta) >= 1.f) ///q0=q1 or q0=q1
{
return q0;
}
float halfTheta = acosf(cosHalfTheta);
float sinHalfTheta = sqrtf(1.f - cosHalfTheta * cosHalfTheta);
if (fabs(sinHalfTheta) < 0.001f)
{ // q0 & q1 180 degrees not defined
return q0*0.5f + q1*0.5f;
}
float srcWeight = sin((1 - t) * halfTheta) / sinHalfTheta;
float dstWeight = sin(t * halfTheta) / sinHalfTheta;
q = srcWeight*q0 + dstWeight*q1;
return q;
}
vector_float3 AAPL_SIMD_OVERLOAD quaternion_rotate_vector(quaternion_float q, vector_float3 v) {
vector_float3 qp = vector_make(q.x, q.y, q.z);
float w = q.w;
return 2 * vector_dot(qp, v) * qp +
((w * w) - vector_dot(qp, qp)) * v +
2 * w * vector_cross(qp, v);
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion_from_matrix3x3(matrix_float3x3 m)
{
quaternion_float q;
float trace = 1 + m.columns[0][0] + m.columns[1][1] + m.columns[2][2];
if(trace > 0)
{
float diagonal = sqrt(trace) * 2.0;
q.x = (m.columns[2][1] - m.columns[1][2]) / diagonal;
q.y = (m.columns[0][2] - m.columns[2][0]) / diagonal;
q.z = (m.columns[1][0] - m.columns[0][1]) / diagonal;
q.w = diagonal / 4.0;
} else if ((m.columns[0][0] > m.columns[1][1] ) &&
(m.columns[0][0] > m.columns[2][2])) {
float diagonal = sqrt( 1.0 + m.columns[0][0] - m.columns[1][1] - m.columns[2][2] ) * 2.0;
q.x = diagonal / 4.0;
q.y = (m.columns[0][1] + m.columns[1][0]) / diagonal;
q.z = (m.columns[0][2] + m.columns[2][0]) / diagonal;
q.w = (m.columns[2][1] - m.columns[1][2]) / diagonal;
} else if ( m.columns[1][1] > m.columns[2][2]) {
float diagonal = sqrt(1.0 + m.columns[1][1] - m.columns[0][0] - m.columns[2][2]) * 2.0;
q.x = (m.columns[0][1] + m.columns[1][0]) / diagonal;
q.y = diagonal / 4.0;
q.z = (m.columns[1][2] + m.columns[2][1]) / diagonal;
q.w = (m.columns[0][2] - m.columns[2][0]) / diagonal;
} else {
float diagonal = sqrt(1.0 + m.columns[2][2] - m.columns[0][0] - m.columns[1][1]) * 2.0;
q.x = (m.columns[0][2] + m.columns[2][0]) / diagonal;
q.y = (m.columns[1][2] + m.columns[2][1]) / diagonal;
q.z = diagonal / 4.0;
q.w = (m.columns[1][0] - m.columns[0][1]) / diagonal;
}
q = quaternion_normalize(q);
return q;
}
static inline quaternion_float AAPL_SIMD_OVERLOAD quaternion_from_direction_vectors(vector_float3 forward, vector_float3 up, int right_handed) {
forward = vector_normalize(forward);
up = vector_normalize(up);
vector_float3 side = vector_normalize(vector_cross(up, forward));
matrix_float3x3 m = { side, up, forward };
quaternion_float q = quaternion_from_matrix3x3(m);
if(right_handed) {
q = q.yxwz;
q.xw = -q.xw;
}
q = vector_normalize(q);
return q;
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion_from_direction_vectors_right_hand(vector_float3 forward, vector_float3 up) {
return quaternion_from_direction_vectors(forward, up, 1);
}
quaternion_float AAPL_SIMD_OVERLOAD quaternion_from_direction_vectors_left_hand(vector_float3 forward, vector_float3 up) {
return quaternion_from_direction_vectors(forward, up, 0);
}
vector_float3 AAPL_SIMD_OVERLOAD forward_direction_vector_from_quaternion(quaternion_float q) {
vector_float3 direction;
direction.x = 2.0 * (q.x*q.z - q.w*q.y);
direction.y = 2.0 * (q.y*q.z + q.w*q.x);
direction.z = 1.0 - 2.0 * ((q.x * q.x) + (q.y * q.y));
direction = vector_normalize(direction);
return direction;
}
vector_float3 AAPL_SIMD_OVERLOAD up_direction_vector_from_quaternion(quaternion_float q) {
vector_float3 direction;
direction.x = 2.0 * (q.x*q.y + q.w*q.z);
direction.y = 1.0 - 2.0 * (q.x*q.x + q.z*q.z);
direction.z = 2.0 * (q.y*q.z - q.w*q.x);
direction = vector_normalize(direction);
// Negate for a right-handed coordinate system
return direction;
}
vector_float3 AAPL_SIMD_OVERLOAD right_direction_vector_from_quaternion(quaternion_float q) {
vector_float3 direction;
direction.x = 1.0 - 2.0 * (q.y * q.y + q.z * q.z);
direction.y = 2.0 * (q.x * q.y - q.w * q.z);
direction.z = 2.0 * (q.x * q.z + q.w * q.y);
direction = vector_normalize(direction);
// Negate for a right-handed coordinate system
return direction;
}

View File

@ -1,8 +1,266 @@
//
// Created by Vicente Ferrari Smith on 27.02.26.
//
/*
See LICENSE folder for this sampleâs licensing information.
#ifndef V_AAPLMATHUTILITIES_H
#define V_AAPLMATHUTILITIES_H
Abstract:
Header for vector, matrix, and quaternion math utility functions useful for 3D graphics rendering.
*/
#endif //V_AAPLMATHUTILITIES_H
#include <stdlib.h>
#include <simd/simd.h>
// Because these are common methods, allow other libraries to overload their implementation.
#define AAPL_SIMD_OVERLOAD __attribute__((__overloadable__))
/// A single-precision quaternion type.
typedef vector_float4 quaternion_float;
/// Given a uint16_t encoded as a 16-bit float, returns a 32-bit float.
float AAPL_SIMD_OVERLOAD float32_from_float16(uint16_t i);
// Given a 32-bit float, returns a uint16_t encoded as a 16-bit float.
uint16_t AAPL_SIMD_OVERLOAD float16_from_float32(float f);
/// Returns the number of degrees in the specified number of radians.
float AAPL_SIMD_OVERLOAD degrees_from_radians(float radians);
/// Returns the number of radians in the specified number of degrees.
float AAPL_SIMD_OVERLOAD radians_from_degrees(float degrees);
// Generates a random float value inside the given range.
inline static float AAPL_SIMD_OVERLOAD random_float(float min, float max)
{
return (((double)random()/RAND_MAX) * (max-min)) + min;
}
/// Generate a random three-component vector with values between min and max.
vector_float3 AAPL_SIMD_OVERLOAD generate_random_vector(float min, float max);
/// Fast random seed.
void AAPL_SIMD_OVERLOAD seedRand(uint32_t seed);
/// Fast integer random.
int32_t AAPL_SIMD_OVERLOAD randi(void);
/// Fast floating-point random.
float AAPL_SIMD_OVERLOAD randf(float x);
/// Returns a vector that is linearly interpolated between the two given vectors.
vector_float3 AAPL_SIMD_OVERLOAD vector_lerp(vector_float3 v0, vector_float3 v1, float t);
/// Returns a vector that is linearly interpolated between the two given vectors.
vector_float4 AAPL_SIMD_OVERLOAD vector_lerp(vector_float4 v0, vector_float4 v1, float t);
/// Converts a unit-norm quaternion into its corresponding rotation matrix.
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix3x3_from_quaternion(quaternion_float q);
/// Constructs a matrix_float3x3 from three rows of three columns with float values.
/// Indices are m<column><row>.
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix_make_rows(float m00, float m10, float m20,
float m01, float m11, float m21,
float m02, float m12, float m22);
/// Constructs a matrix_float4x4 from four rows of four columns with float values.
/// Indices are m<column><row>.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_make_rows(float m00, float m10, float m20, float m30,
float m01, float m11, float m21, float m31,
float m02, float m12, float m22, float m32,
float m03, float m13, float m23, float m33);
/// Constructs a matrix_float3x3 from 3 vector_float3 column vectors.
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix_make_columns(vector_float3 col0,
vector_float3 col1,
vector_float3 col2);
/// Constructs a matrix_float4x4 from 4 vector_float4 column vectors.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_make_columns(vector_float4 col0,
vector_float4 col1,
vector_float4 col2,
vector_float4 col3);
/// Constructs a rotation matrix from the given angle and axis.
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix3x3_rotation(float radians, vector_float3 axis);
/// Constructs a rotation matrix from the given angle and axis.
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix3x3_rotation(float radians, float x, float y, float z);
/// Constructs a scaling matrix with the specified scaling factors.
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix3x3_scale(float x, float y, float z);
/// Constructs a scaling matrix, using the given vector as an array of scaling factors.
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix3x3_scale(vector_float3 s);
/// Extracts the upper-left 3x3 submatrix of the given 4x4 matrix.
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix3x3_upper_left(matrix_float4x4 m);
/// Returns the inverse of the transpose of the given matrix.
matrix_float3x3 AAPL_SIMD_OVERLOAD matrix_inverse_transpose(matrix_float3x3 m);
/// Constructs a homogeneous rotation matrix from the given angle and axis.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_from_quaternion(quaternion_float q);
/// Constructs a rotation matrix from the provided angle and axis
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_rotation(float radians, vector_float3 axis);
/// Constructs a rotation matrix from the given angle and axis.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_rotation(float radians, float x, float y, float z);
/// Constructs an identity matrix.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_identity(void);
/// Constructs a scaling matrix with the given scaling factors.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_scale(float sx, float sy, float sz);
/// Constructs a scaling matrix, using the given vector as an array of scaling factors.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_scale(vector_float3 s);
/// Constructs a translation matrix that translates by the vector (tx, ty, tz).
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_translation(float tx, float ty, float tz);
/// Constructs a translation matrix that translates by the vector (t.x, t.y, t.z).
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_translation(vector_float3 t);
/// Constructs a translation matrix that scales by the vector (s.x, s.y, s.z)
/// and translates by the vector (t.x, t.y, t.z).
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix4x4_scale_translation(vector_float3 s, vector_float3 t);
/// Starting with left-hand world coordinates, constructs a view matrix that is
/// positioned at (eyeX, eyeY, eyeZ) and looks toward (centerX, centerY, centerZ),
/// with the vector (upX, upY, upZ) pointing up for a left-hand coordinate system.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_look_at_left_hand(float eyeX, float eyeY, float eyeZ,
float centerX, float centerY, float centerZ,
float upX, float upY, float upZ);
/// Starting with left-hand world coordinates, constructs a view matrix that is
/// positioned at (eye) and looks toward (target), with the vector (up) pointing
/// up for a left-hand coordinate system.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_look_at_left_hand(vector_float3 eye,
vector_float3 target,
vector_float3 up);
/// Starting with right-hand world coordinates, constructs a view matrix that is
/// positioned at (eyeX, eyeY, eyeZ) and looks toward (centerX, centerY, centerZ),
/// with the vector (upX, upY, upZ) pointing up for a right-hand coordinate system.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_look_at_right_hand(float eyeX, float eyeY, float eyeZ,
float centerX, float centerY, float centerZ,
float upX, float upY, float upZ);
/// Starting with right-hand world coordinates, constructs a view matrix that is
/// positioned at (eye) and looks toward (target), with the vector (up) pointing
/// up for a right-hand coordinate system.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_look_at_right_hand(vector_float3 eye,
vector_float3 target,
vector_float3 up);
/// Constructs a symmetric orthographic projection matrix, from left-hand eye
/// coordinates to left-hand clip coordinates.
/// That maps (left, top) to (-1, 1), (right, bottom) to (1, -1), and (nearZ, farZ) to (0, 1).
/// The first four arguments are signed eye coordinates.
/// nearZ and farZ are absolute distances from the eye to the near and far clip planes.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_ortho_left_hand(float left, float right, float bottom, float top, float nearZ, float farZ);
/// Constructs a symmetric orthographic projection matrix, from right-hand eye
/// coordinates to right-hand clip coordinates.
/// That maps (left, top) to (-1, 1), (right, bottom) to (1, -1), and (nearZ, farZ) to (0, 1).
/// The first four arguments are signed eye coordinates.
/// nearZ and farZ are absolute distances from the eye to the near and far clip planes.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_ortho_right_hand(float left, float right, float bottom, float top, float nearZ, float farZ);
/// Constructs a symmetric perspective projection matrix, from left-hand eye
/// coordinates to left-hand clip coordinates, with a vertical viewing angle of
/// fovyRadians, the given aspect ratio, and the given absolute near and far
/// z distances from the eye.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_perspective_left_hand(float fovyRadians, float aspect, float nearZ, float farZ);
/// Constructs a symmetric perspective projection matrix, from right-hand eye
/// coordinates to right-hand clip coordinates, with a vertical viewing angle of
/// fovyRadians, the given aspect ratio, and the given absolute near and far
/// z distances from the eye.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_perspective_right_hand(float fovyRadians, float aspect, float nearZ, float farZ);
/// Construct a general frustum projection matrix, from right-hand eye
/// coordinates to left-hand clip coordinates.
/// The bounds left, right, bottom, and top, define the visible frustum at the near clip plane.
/// The first four arguments are signed eye coordinates.
/// nearZ and farZ are absolute distances from the eye to the near and far clip planes.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_perspective_frustum_right_hand(float left, float right, float bottom, float top, float nearZ, float farZ);
/// Returns the inverse of the transpose of the given matrix.
matrix_float4x4 AAPL_SIMD_OVERLOAD matrix_inverse_transpose(matrix_float4x4 m);
/// Constructs an identity quaternion.
quaternion_float AAPL_SIMD_OVERLOAD quaternion_identity(void);
/// Constructs a quaternion of the form w + xi + yj + zk.
quaternion_float AAPL_SIMD_OVERLOAD quaternion(float x, float y, float z, float w);
/// Constructs a quaternion of the form w + v.x*i + v.y*j + v.z*k.
quaternion_float AAPL_SIMD_OVERLOAD quaternion(vector_float3 v, float w);
/// Constructs a unit-norm quaternion that represents rotation by the given angle about the axis (x, y, z).
quaternion_float AAPL_SIMD_OVERLOAD quaternion(float radians, float x, float y, float z);
/// Constructs a unit-norm quaternion that represents rotation by the given angle about the specified axis.
quaternion_float AAPL_SIMD_OVERLOAD quaternion(float radians, vector_float3 axis);
/// Constructs a unit-norm quaternion from the given matrix.
/// The result is undefined if the matrix does not represent a pure rotation.
quaternion_float AAPL_SIMD_OVERLOAD quaternion(matrix_float3x3 m);
/// Constructs a unit-norm quaternion from the given matrix.
/// The result is undefined if the matrix does not represent a pure rotation.
quaternion_float AAPL_SIMD_OVERLOAD quaternion(matrix_float4x4 m);
/// Returns the length of the given quaternion.
float AAPL_SIMD_OVERLOAD quaternion_length(quaternion_float q);
float AAPL_SIMD_OVERLOAD quaternion_length_squared(quaternion_float q);
/// Returns the rotation axis of the given unit-norm quaternion.
vector_float3 AAPL_SIMD_OVERLOAD quaternion_axis(quaternion_float q);
/// Returns the rotation angle of the given unit-norm quaternion.
float AAPL_SIMD_OVERLOAD quaternion_angle(quaternion_float q);
/// Returns a quaternion from the given rotation axis and angle, in radians.
quaternion_float AAPL_SIMD_OVERLOAD quaternion_from_axis_angle(vector_float3 axis, float radians);
/// Returns a quaternion from the given 3x3 rotation matrix.
quaternion_float AAPL_SIMD_OVERLOAD quaternion_from_matrix3x3(matrix_float3x3 m);
/// Returns a quaternion from the given Euler angle, in radians.
quaternion_float AAPL_SIMD_OVERLOAD quaternion_from_euler(vector_float3 euler);
/// Returns a unit-norm quaternion.
quaternion_float AAPL_SIMD_OVERLOAD quaternion_normalize(quaternion_float q);
/// Returns the inverse quaternion of the given quaternion.
quaternion_float AAPL_SIMD_OVERLOAD quaternion_inverse(quaternion_float q);
/// Returns the conjugate quaternion of the given quaternion.
quaternion_float AAPL_SIMD_OVERLOAD quaternion_conjugate(quaternion_float q);
/// Returns the product of the two given quaternions.
quaternion_float AAPL_SIMD_OVERLOAD quaternion_multiply(quaternion_float q0, quaternion_float q1);
/// Returns the quaternion that results from spherically interpolating between the two given quaternions.
quaternion_float AAPL_SIMD_OVERLOAD quaternion_slerp(quaternion_float q0, quaternion_float q1, float t);
/// Returns the vector that results from rotating the given vector by the given unit-norm quaternion.
vector_float3 AAPL_SIMD_OVERLOAD quaternion_rotate_vector(quaternion_float q, vector_float3 v);
/// Returns the quaternion for the given forward and up vectors for right-hand coordinate systems.
quaternion_float AAPL_SIMD_OVERLOAD quaternion_from_direction_vectors_right_hand(vector_float3 forward, vector_float3 up);
/// Returns the quaternion for the given forward and up vectors for left-hand coordinate systems.
quaternion_float AAPL_SIMD_OVERLOAD quaternion_from_direction_vectors_left_hand(vector_float3 forward, vector_float3 up);
/// Returns a vector in the +Z direction for the given quaternion.
vector_float3 AAPL_SIMD_OVERLOAD forward_direction_vector_from_quaternion(quaternion_float q);
/// Returns a vector in the +Y direction for the given quaternion (for a left-handed coordinate system,
/// negate for a right-hand coordinate system).
vector_float3 AAPL_SIMD_OVERLOAD up_direction_vector_from_quaternion(quaternion_float q);
/// Returns a vector in the +X direction for the given quaternion (for a left-hand coordinate system,
/// negate for a right-hand coordinate system).
vector_float3 AAPL_SIMD_OVERLOAD right_direction_vector_from_quaternion(quaternion_float q);

View File

@ -2,8 +2,18 @@
// Created by Vicente Ferrari Smith on 26.02.26.
//
#include "init.h"
#define NS_PRIVATE_IMPLEMENTATION
#define MTL_PRIVATE_IMPLEMENTATION
#define MTK_PRIVATE_IMPLEMENTATION
#define CA_PRIVATE_IMPLEMENTATION
#include <Foundation/Foundation.hpp>
#include <Metal/Metal.hpp>
#include <QuartzCore/QuartzCore.hpp>
#include "metal.h"
#include "../graphics.h"
#include "../texture.h"
#include <print>
#include <iostream>
#include <fstream>
@ -12,20 +22,41 @@
#include <GLFW/glfw3native.h>
#include <objc/message.h>
#include <objc/objc.h>
#include "vertex_data.h"
extern int32_t window_width;
extern int32_t window_height;
Device metal_device{};
MTL::Buffer* triangle_vertex_buffer{};
MTL::CommandQueue *queue{};
CA::MetalLayer *metal_layer{};
MTL::RenderPipelineState *pipeline_state{};
CA::MetalDrawable *metal_drawable{};
MTL::CommandBuffer* metal_command_buffer{};
MTL::Function *vertex_shader{};
MTL::Function *fragment_shader{};
Renderer renderer;
void create_window(GLFWwindow *window) {
void upload_texture(
const int w,
const int h,
const void *pixels,
Texture *texture)
{
MTL::TextureDescriptor *td = MTL::TextureDescriptor::alloc()->init();
td->setPixelFormat(MTL::PixelFormatRGBA8Unorm);
td->setWidth(w);
td->setHeight(h);
td->setStorageMode(MTL::StorageModeShared);
td->setUsage(MTL::TextureUsageShaderRead);
texture->p_texture->texture = metal_device.device->newTexture(td);
MTL::Region region = MTL::Region(0, 0, 0, w, h, 1);
NS::UInteger bytesPerRow = 4 * w;
texture->p_texture->texture->replaceRegion(region, 0, pixels, bytesPerRow);
td->release();
}
void create_metal_layer(GLFWwindow *window) {
void *ns_window = glfwGetCocoaWindow(window);
if (!ns_window) {
throw std::runtime_error("Failed to get Cocoa window from GLFWwindow");
@ -51,44 +82,11 @@ void create_window(GLFWwindow *window) {
metal_layer->setColorspace(p3Space);
}
void encode_render_command(MTL::RenderCommandEncoder *renderCommandEncoder) {
renderCommandEncoder->setRenderPipelineState(pipeline_state);
renderCommandEncoder->setVertexBuffer(triangle_vertex_buffer, 0, 0);
MTL::PrimitiveType typeTriangle = MTL::PrimitiveTypeTriangle;
NS::UInteger vertexStart = 0;
NS::UInteger vertexCount = 6;
renderCommandEncoder->drawPrimitives(typeTriangle, vertexStart, vertexCount);
}
void send_render_command() {
metal_command_buffer = queue->commandBuffer();
MTL::RenderPassDescriptor* renderPassDescriptor = MTL::RenderPassDescriptor::alloc()->init();
MTL::RenderPassColorAttachmentDescriptor *cd = renderPassDescriptor->colorAttachments()->object(0);
cd->setTexture(metal_drawable->texture());
cd->setLoadAction(MTL::LoadActionClear);
cd->setClearColor(MTL::ClearColor(
100.0f / 255.0f,
149.0f / 255.0f,
237.0f / 255.0f,
1.0
));
cd->setStoreAction(MTL::StoreActionStore);
MTL::RenderCommandEncoder* renderCommandEncoder = metal_command_buffer->renderCommandEncoder(renderPassDescriptor);
encode_render_command(renderCommandEncoder);
renderCommandEncoder->endEncoding();
metal_command_buffer->presentDrawable(metal_drawable);
metal_command_buffer->commit();
metal_command_buffer->waitUntilCompleted();
renderPassDescriptor->release();
}
void LoadMetalShader(const std::string &shader_path,
void load_metal_shader(const std::string &shader_path,
const std::string &vertex_fn_name,
const std::string &fragment_fn_name)
const std::string &fragment_fn_name,
MTL::Function **vertex_shader,
MTL::Function **fragment_shader)
{
NS::Error *error = nullptr;
MTL::Library *library = nullptr;
@ -137,80 +135,72 @@ void LoadMetalShader(const std::string &shader_path,
}
NS::String *vname = NS::String::string(vertex_fn_name.c_str(), NS::UTF8StringEncoding);
NS::String *fname = NS::String::string(fragment_fn_name.c_str(), NS::UTF8StringEncoding);
vertex_shader = library->newFunction(vname);
fragment_shader = library->newFunction(fname);
*vertex_shader = library->newFunction(vname);
*fragment_shader = library->newFunction(fname);
if (vertex_shader == nullptr || fragment_shader == nullptr) {
if (*vertex_shader == nullptr || *fragment_shader == nullptr) {
throw std::runtime_error("Failed to create Metal shader functions");
}
library->release();
}
void create_render_pipeline() {
LoadMetalShader("shaders/shader.metal", "vertex_main", "fragment_main");
MTL::RenderPipelineDescriptor* renderPipelineDescriptor = MTL::RenderPipelineDescriptor::alloc()->init();
renderPipelineDescriptor->setLabel(NS::String::string("Triangle Rendering Pipeline", NS::ASCIIStringEncoding));
renderPipelineDescriptor->setVertexFunction(vertex_shader);
renderPipelineDescriptor->setFragmentFunction(fragment_shader);
assert(renderPipelineDescriptor);
const MTL::PixelFormat pixel_format = metal_layer->pixelFormat();
renderPipelineDescriptor->colorAttachments()->object(0)->setPixelFormat(pixel_format);
NS::Error* error;
pipeline_state = metal_device.device->newRenderPipelineState(renderPipelineDescriptor, &error);
renderPipelineDescriptor->release();
}
void create_command_queue() {
queue = metal_device.device->newCommandQueue();
}
void create_triangle() {
VertexData square_vertices[] = {
{{-0.5, -0.5}, {1.0, 0.0, 0.0, 1.0}},
{{0.5, -0.5}, {0.0, 1.0, 0.0, 1.0}},
{{0.5, 0.5}, {0.0, 0.0, 1.0, 1.0}},
{{0.5, 0.5}, {0.0, 0.0, 1.0, 1.0}},
{{-0.5, 0.5}, {0.0, 1.0, 0.0, 1.0}},
{{-0.5, -0.5}, {1.0, 0.0, 0.0, 1.0}},
};
triangle_vertex_buffer = metal_device.device->newBuffer(&square_vertices,
sizeof(square_vertices),
MTL::ResourceStorageModeShared);
}
void graphics_init(GLFWwindow *window) {
std::println("wow, we are on macos!! crazy!!");
create_device();
create_window(window);
create_triangle();
create_command_queue();
create_render_pipeline();
}
void graphics_deinit() {
}
void begin_frame() {
}
void end_frame() {
auto pPool = NS::AutoreleasePool::alloc()->init();
metal_drawable = metal_layer->nextDrawable();
send_render_command();
pPool->release();
}
void create_device() {
metal_device.device = MTL::CreateSystemDefaultDevice();
}
void platform_graphics_init(GLFWwindow *window) {
std::println("wow, we are on macos!! crazy!!");
auto slangTargets{ std::to_array<slang::TargetDesc>({ {
.format = SLANG_METAL,
.profile = slangGlobalSession->findProfile("metallib_2_4"),
} })};
// auto slangOptions{ std::to_array<slang::CompilerOptionEntry>({ {
// slang::CompilerOptionName::EmitSpirvDirectly,
// {slang::CompilerOptionValueKind::Int, 1}
// } })};
const char *search_paths[] = {"shaders"};
slang::SessionDesc slangSessionDesc{
.targets = slangTargets.data(),
.targetCount = SlangInt(slangTargets.size()),
.searchPaths = search_paths,
.searchPathCount = 1,
// .defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_COLUMN_MAJOR,
};
slangGlobalSession->createSession(slangSessionDesc, slangSession.writeRef());
create_device();
create_metal_layer(window);
create_command_queue();
renderer = Renderer(window);
}
void graphics_deinit() {
queue->release();
metal_layer->release();
metal_device.device->release();
}
void begin_frame() {
renderer.begin_frame();
}
void end_frame(GLFWwindow *window) {
auto pPool = NS::AutoreleasePool::alloc()->init();
metal_drawable = metal_layer->nextDrawable();
renderer.end_frame(window);
pPool->release();
}
void submit_sprite(glm::vec2 pos, const sprite_t &sprite) {
renderer.submit_sprite({pos.x, pos.y}, sprite);
}

View File

@ -2,8 +2,8 @@
// Created by Vicente Ferrari Smith on 26.02.26.
//
#ifndef M_INIT_H
#define M_INIT_H
#ifndef V_METAL_H
#define V_METAL_H
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_COCOA
@ -17,6 +17,18 @@ struct Device {
MTL::Device *device;
};
void create_device();
struct PlatformTexture {
MTL::Texture *texture;
};
#endif //M_INIT_H
struct Queue {
MTL::CommandQueue *queue;
};
void load_metal_shader(const std::string &shader_path,
const std::string &vertex_fn_name,
const std::string &fragment_fn_name,
MTL::Function **vertex_shader,
MTL::Function **fragment_shader);
#endif //V_METAL_H

File diff suppressed because it is too large Load Diff

View File

@ -5,25 +5,13 @@
#ifndef V_RENDERER_H
#define V_RENDERER_H
#include "init.h"
#include <volk/volk.h>
#include "metal.h"
#include <GLFW/glfw3.h>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/ext/matrix_clip_space.hpp>
#include "glm/gtx/string_cast.hpp"
#include <vma/vk_mem_alloc.h>
#include "../sprite.h"
#include "../texture.h"
#include <misc.h>
#include <array>
#include <span>
#include <slang/slang.h>
#include <slang/slang-com-ptr.h>
#include <simd/simd.h>
inline Slang::ComPtr<slang::IGlobalSession> slangGlobalSession;
static const int kMaxFramesInFlight = 3;
enum class PROJECTION_TYPE : uint8_t {
NONE,
@ -34,32 +22,6 @@ enum class PROJECTION_TYPE : uint8_t {
COUNT,
};
struct vertex_p2_s2_st2_col4_a1_u32 {
glm::vec2 pos;
glm::vec2 scale;
glm::vec2 uv;
glm::vec4 color;
float alpha;
uint32_t textureID;
static VkVertexInputBindingDescription getBindingDescription() {
return {0, sizeof(vertex_p2_s2_st2_col4_a1_u32), VK_VERTEX_INPUT_RATE_VERTEX};
}
static std::array<VkVertexInputAttributeDescription, 6> getAttributeDescriptions() {
return {
{
{0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(vertex_p2_s2_st2_col4_a1_u32, pos)},
{1, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(vertex_p2_s2_st2_col4_a1_u32, scale)},
{2, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(vertex_p2_s2_st2_col4_a1_u32, uv)},
{3, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(vertex_p2_s2_st2_col4_a1_u32, color)},
{4, 0, VK_FORMAT_R32_SFLOAT, offsetof(vertex_p2_s2_st2_col4_a1_u32, alpha)},
{5, 0, VK_FORMAT_R32_UINT, offsetof(vertex_p2_s2_st2_col4_a1_u32, textureID)},
}
};
}
};
// commands
enum class PipelineType : uint8_t {
@ -72,24 +34,24 @@ enum class PipelineType : uint8_t {
};
struct TexturedQuadCmd {
glm::vec2 position;
glm::vec2 size;
glm::vec2 uvMin;
glm::vec2 uvMax;
glm::vec4 color;
uint16_t textureID;
simd::float2 pos;
simd::float2 scale;
simd::float2 uv0;
simd::float2 uv1;
simd::float4 colour;
MTL::Texture *texture;
};
struct ColoredQuadCmd {
glm::vec2 pos;
glm::vec2 scale;
glm::vec4 color;
simd::float2 pos;
simd::float2 scale;
simd::float4 colour;
};
struct LineCmd {
glm::vec2 start;
glm::vec2 end;
glm::vec4 color;
simd::float2 start;
simd::float2 end;
simd::float4 color;
};
// struct TextCmd {
@ -99,11 +61,11 @@ struct LineCmd {
// glm::vec4 color;
// };
struct ChunkCmd {
VkBuffer vertexBuffer;
VkBuffer indexBuffer;
uint32_t indexCount;
};
//struct ChunkCmd {
// VkBuffer vertexBuffer;
// VkBuffer indexBuffer;
// uint32_t indexCount;
//};
struct SortKey {
uint16_t depth; // world Z or Y-sorted depth
@ -123,280 +85,101 @@ struct RenderCommand {
ColoredQuadCmd colored_quad;
LineCmd line;
// TextCmd text;
ChunkCmd chunk;
// ChunkCmd chunk;
};
};
////////////////////////////////////////////////////////////////////////////////////////////////
struct AllocatedBuffer {
VkBuffer buffer;
VmaAllocation allocation;
VmaAllocationInfo info;
};
// struct AllocatedBuffer {
// VkBuffer buffer;
// VmaAllocation allocation;
// VmaAllocationInfo info;
// };
struct GPUMeshBuffers {
AllocatedBuffer indexBuffer;
AllocatedBuffer vertexBuffer;
VkDeviceAddress vertexBufferAddress;
};
// struct GPUMeshBuffers {
// AllocatedBuffer indexBuffer;
// AllocatedBuffer vertexBuffer;
// VkDeviceAddress vertexBufferAddress;
// };
struct Renderer {
std::vector<RenderCommand> commands{};
VkDescriptorSetLayout descriptor_set_layout{};
VkPipelineLayout pipelineLayout{};
VkPipeline textured_quad_pipeline{};
VkPipeline colored_quad_pipeline{};
VkPipeline line_pipeline{};
VkPipeline text_pipeline{};
VkPipeline chunk_pipeline{};
VkDescriptorSet set{};
VkSampler defaultSampler{};
MTL::RenderPipelineState *textured_quad_pipeline{};
MTL::RenderPipelineState *colored_quad_pipeline{};
MTL::RenderPipelineState *line_pipeline{};
MTL::RenderPipelineState *text_pipeline{};
MTL::RenderPipelineState *chunk_pipeline{};
uint32_t nextTextureSlot = 0;
MTL::CommandBuffer *command_buffer{};
struct Frame {
VkCommandPool commandPool{};
VkCommandBuffer command_buffer{};
VkSemaphore imageAvailable{};
VkFence in_flight_fence{};
AllocatedBuffer vertexBuffer{};
MTL::Buffer *vertex_buffer{};
MTL::Buffer *uniform_buffer{};
};
std::vector<Frame> frames;
uint32_t currentFrame = 0;
Frame frames[kMaxFramesInFlight];
VkDescriptorPool descriptorPool{};
std::vector<VkDescriptorSet> textureSets{};
dispatch_semaphore_t frame_semaphore;
uint8_t current_frame = 0;
// struct Frame {
// VkCommandPool commandPool{};
// VkCommandBuffer command_buffer{};
//
// VkSemaphore imageAvailable{};
// VkFence in_flight_fence{};
//
// AllocatedBuffer vertexBuffer{};
// };
//
// std::vector<Frame> frames;
// uint32_t currentFrame = 0;
//
// VkDescriptorPool descriptorPool{};
// std::vector<VkDescriptorSet> textureSets{};
//
void begin_frame();
void end_frame();
void end_frame(GLFWwindow *window);
void flush();
void submit_sprite(glm::vec2 pos, const sprite_t &sprite);
void submit_quad(glm::vec2 pos, glm::vec2 scale);
void submit_sprite(simd::float2 pos, const sprite_t &sprite);
void submit_quad(simd::float2 pos, simd::float2 scale);
Renderer() = default;
explicit Renderer(GLFWwindow *window);
void create_pipeline_layout();
void createFrameResources();
void create_default_sampler();
void recordCommandBuffer(
VkCommandBuffer cmd,
VkImage image,
VkImageView imageView,
VkExtent2D extent,
VkImageLayout oldLayout,
const Frame &frame,
const std::vector<vertex_p2_s2_st2_col4_a1_u32> &vertices) const;
void immediate_submit(std::function<void(VkCommandBuffer)>&& func) const;
void transition_image_layout(VkCommandBuffer cmd, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) const;
VkImageView create_image_view(VkImage image, VkFormat format) const;
AllocatedBuffer create_buffer(size_t allocSize, VkBufferUsageFlags usage, VmaMemoryUsage memoryUsage);
void destroy_buffer(const AllocatedBuffer& buffer);
// GPUMeshBuffers uploadMesh(std::span<uint32_t> indices, std::span<vertex_p2_st2_col4_a1_u32> vertices);
void upload_vertex_buffer(
VkCommandBuffer cmd,
const Frame &frame,
std::span<const vertex_p2_s2_st2_col4_a1_u32> vertices) const;
[[nodiscard]] VkPipeline get_pipeline(PipelineType type) const;
// void bind_material(VkCommandBuffer cmd, uint16_t materialID);
void create_descriptor_pool();
void update_bindless_slot(uint32_t slot, VkImageView view, VkSampler sampler) const;
// Returns the resource info so the Manager can store it
void upload_texture(
int w,
int h,
const void* pixels,
VkImage *image,
VmaAllocation *allocation,
VkImageView *view,
uint32_t *descriptor_index);
template <typename T>
VkPipeline create_graphics_pipeline(
VkDevice device,
VkPipelineLayout layout,
VkFormat colorFormat,
// VkShaderModule vertShader,
// VkShaderModule fragShader,
VkPrimitiveTopology topology,
bool enableBlending)
{
auto slangTargets{ std::to_array<slang::TargetDesc>({ {
.format = SLANG_SPIRV,
.profile = slangGlobalSession->findProfile("spirv_1_4")
} })};
auto slangOptions{ std::to_array<slang::CompilerOptionEntry>({ {
slang::CompilerOptionName::EmitSpirvDirectly,
{slang::CompilerOptionValueKind::Int, 1}
} })};
slang::SessionDesc slangSessionDesc{
.targets = slangTargets.data(),
.targetCount = SlangInt(slangTargets.size()),
.defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_COLUMN_MAJOR,
.compilerOptionEntries = slangOptions.data(),
.compilerOptionEntryCount = uint32_t(slangOptions.size())
};
Slang::ComPtr<slang::ISession> slangSession;
slangGlobalSession->createSession(slangSessionDesc, slangSession.writeRef());
Slang::ComPtr<slang::IModule> slangModule{
slangSession->loadModuleFromSource("triangle", "shaders/shader.slang", nullptr, nullptr)
};
Slang::ComPtr<ISlangBlob> spirv;
slangModule->getTargetCode(0, spirv.writeRef());
VkShaderModuleCreateInfo shaderModuleCI{
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = spirv->getBufferSize(),
.pCode = (uint32_t*)spirv->getBufferPointer()
};
VkShaderModule shaderModule{};
vkCreateShaderModule(device, &shaderModuleCI, nullptr, &shaderModule);
auto vsCode = loadFile("shaders/triangle.vert.spv");
auto fsCode = loadFile("shaders/triangle.frag.spv");
VkShaderModuleCreateInfo smci{
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO
};
smci.codeSize = vsCode.size();
smci.pCode = reinterpret_cast<uint32_t*>(vsCode.data());
// VkShaderModule vs;
// vkCreateShaderModule(device, &smci, nullptr, &vs);
smci.codeSize = fsCode.size();
smci.pCode = reinterpret_cast<uint32_t*>(fsCode.data());
// VkShaderModule fs;
// vkCreateShaderModule(device, &smci, nullptr, &fs);
// --- Shaders ---
std::vector<VkPipelineShaderStageCreateInfo> shaderStages{
{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = shaderModule, .pName = "main"},
{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.module = shaderModule, .pName = "main" }
};
// --- Vertex Input (Generic) ---
auto binding = T::getBindingDescription();
auto attrs = T::getAttributeDescriptions();
// --- Vertex Input (Matching our vertex_p2_st2_col4 struct) ---
VkPipelineVertexInputStateCreateInfo vi{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = &binding,
.vertexAttributeDescriptionCount = attrs.size(),
.pVertexAttributeDescriptions = attrs.data(),
};
// --- Input Assembly (Changes based on Topology parameter) ---
VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
ia.topology = topology;
// --- Blending (Changes based on enableBlending parameter) ---
VkPipelineColorBlendAttachmentState colorBlend{
.blendEnable = enableBlending ? VK_TRUE : VK_FALSE,
.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
.colorBlendOp = VK_BLEND_OP_ADD,
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
.alphaBlendOp = VK_BLEND_OP_ADD,
.colorWriteMask = 0xF
};
// --- Boilerplate (Standard 2D Defaults) ---
VkPipelineViewportStateCreateInfo vp{
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
nullptr,
0,
1,
nullptr,
1,
nullptr
};
VkPipelineRasterizationStateCreateInfo rs{
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
nullptr,
0,
0,
0,
VK_POLYGON_MODE_FILL,
VK_CULL_MODE_NONE,
VK_FRONT_FACE_COUNTER_CLOCKWISE,
0,
0,
0,
0,
1.0f
};
VkPipelineMultisampleStateCreateInfo ms{
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
nullptr,
0,
VK_SAMPLE_COUNT_1_BIT
};
VkPipelineColorBlendStateCreateInfo cb{
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
nullptr,
0,
0,
VK_LOGIC_OP_AND,
1,
&colorBlend
};
VkDynamicState dyns[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineDynamicStateCreateInfo ds{
VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
nullptr,
0,
2,
dyns
};
VkPipelineRenderingCreateInfo rci{
VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
nullptr,
0,
1,
&colorFormat
};
VkGraphicsPipelineCreateInfo gpci{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = &rci,
.stageCount = (uint32_t) shaderStages.size(),
.pStages = shaderStages.data(),
.pVertexInputState = &vi,
.pInputAssemblyState = &ia,
.pViewportState = &vp,
.pRasterizationState = &rs,
.pMultisampleState = &ms,
.pColorBlendState = &cb,
.pDynamicState = &ds,
.layout = layout
};
VkPipeline pipeline;
vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &gpci, nullptr, &pipeline);
return pipeline;
}
void create_render_pipeline();
void send_render_command(GLFWwindow *window);
void encode_render_command(GLFWwindow *window, MTL::RenderCommandEncoder *render_command_encoder);
// void create_pipeline_layout();
// void createFrameResources();
// void create_default_sampler();
// void recordCommandBuffer(
// VkCommandBuffer cmd,
// VkImage image,
// VkImageView imageView,
// VkExtent2D extent,
// VkImageLayout oldLayout,
// const Frame &frame,
// const std::vector<vertex_p2_s2_st2_col4_a1_u32> &vertices) const;
// void immediate_submit(std::function<void(VkCommandBuffer)>&& func) const;
// void transition_image_layout(VkCommandBuffer cmd, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) const;
// VkImageView create_image_view(VkImage image, VkFormat format) const;
// AllocatedBuffer create_buffer(size_t allocSize, VkBufferUsageFlags usage, VmaMemoryUsage memoryUsage);
// void destroy_buffer(const AllocatedBuffer& buffer);
// // GPUMeshBuffers uploadMesh(std::span<uint32_t> indices, std::span<vertex_p2_st2_col4_a1_u32> vertices);
// void upload_vertex_buffer(
// VkCommandBuffer cmd,
// const Frame &frame,
// std::span<const vertex_p2_s2_st2_col4_a1_u32> vertices) const;
//
[[nodiscard]] MTL::RenderPipelineState *get_pipeline(PipelineType type) const;
// // void bind_material(VkCommandBuffer cmd, uint16_t materialID);
// void create_descriptor_pool();
// void update_bindless_slot(uint32_t slot, VkImageView view, VkSampler sampler) const;
};
#endif //V_RENDERER_H

View File

@ -2,7 +2,56 @@
// Created by Vicente Ferrari Smith on 27.02.26.
//
#ifndef V_VERTEX_DATA_H
#define V_VERTEX_DATA_H
#pragma once
#include <simd/simd.h>
#endif //V_VERTEX_DATA_H
using namespace simd;
struct vertex_p2_s2_uv2_c4_a1 {
float2 pos;
float2 scale;
float2 uv;
float4 color;
float alpha;
static MTL::VertexDescriptor* vertexDescriptor() {
MTL::VertexDescriptor* vd = MTL::VertexDescriptor::alloc()->init();
// ATTRIBUTE 0 — float2 pos
vd->attributes()->object(0)->setFormat(MTL::VertexFormatFloat2);
vd->attributes()->object(0)->setOffset(offsetof(vertex_p2_s2_uv2_c4_a1, pos));
vd->attributes()->object(0)->setBufferIndex(0);
// ATTRIBUTE 1 — float2 scale
vd->attributes()->object(1)->setFormat(MTL::VertexFormatFloat2);
vd->attributes()->object(1)->setOffset(offsetof(vertex_p2_s2_uv2_c4_a1, scale));
vd->attributes()->object(1)->setBufferIndex(0);
// ATTRIBUTE 2 — float2 uv
vd->attributes()->object(2)->setFormat(MTL::VertexFormatFloat2);
vd->attributes()->object(2)->setOffset(offsetof(vertex_p2_s2_uv2_c4_a1, uv));
vd->attributes()->object(2)->setBufferIndex(0);
// ATTRIBUTE 3 — float4 color
vd->attributes()->object(3)->setFormat(MTL::VertexFormatFloat4);
vd->attributes()->object(3)->setOffset(offsetof(vertex_p2_s2_uv2_c4_a1, color));
vd->attributes()->object(3)->setBufferIndex(0);
// ATTRIBUTE 4 — float alpha
vd->attributes()->object(4)->setFormat(MTL::VertexFormatFloat);
vd->attributes()->object(4)->setOffset(offsetof(vertex_p2_s2_uv2_c4_a1, alpha));
vd->attributes()->object(4)->setBufferIndex(0);
// Layout for buffer 0
vd->layouts()->object(0)->setStride(sizeof(vertex_p2_s2_uv2_c4_a1));
vd->layouts()->object(0)->setStepFunction(MTL::VertexStepFunctionPerVertex);
return vd;
}
};
struct TransformationData {
float4x4 model_matrix;
float4x4 view_matrix;
float4x4 perspective_matrix;
};

View File

@ -2,4 +2,4 @@
// Created by Vicente Ferrari Smith on 14.02.26.
//
#include "../sprite.h"
#include "sprite.h"

View File

@ -2,8 +2,8 @@
// Created by Vicente Ferrari Smith on 14.02.26.
//
#ifndef V_SPRITE_H
#define V_SPRITE_H
#ifndef SPRITE_H
#define SPRITE_H
#include <glm/glm.hpp>
#include "texture_sheet.h"
@ -17,8 +17,7 @@ struct sprite_t {
bool window_space;
bool maintain_ar;
texture_sheet_id texture_sheet;
texture_cell_id texture_cell;
texture_id texture;
};
#endif //V_SPRITE_H
#endif //SPRITE_H

View File

@ -1,3 +1,37 @@
//
// Created by Vicente Ferrari Smith on 01.03.26.
//
#include "texture.h"
#include "graphics.h"
TextureManager::TextureManager() {
}
Texture TextureManager::load(const std::string& path) {
// Dedup: Don't load the same file twice!
// if (path_to_id.contains(path)) return path_to_id[path];
int w, h, ch;
unsigned char* data = stbi_load(path.c_str(), &w, &h, &ch, STBI_rgb_alpha);
// Tell the renderer to make the GPU version
Texture res;
res.width = w;
res.height = h;
res.channels = STBI_rgb_alpha;
res.srgb = true;
upload_texture(w, h, data, &res);
stbi_image_free(data);
res.id = path;
res.path = path;
res.uploaded = true;
textures[path] = res;
// path_to_id[path] = id;
return res; // This is the textureID for your sprites
}

View File

@ -2,15 +2,15 @@
// Created by Vicente Ferrari Smith on 14.02.26.
//
#ifndef V_TEXTURE_H
#define V_TEXTURE_H
#ifndef TEXTURE_H
#define TEXTURE_H
#include <string>
#include <unordered_map>
#include <volk/volk.h>
#include <vma/vk_mem_alloc.h>
#include <stb_image.h>
struct Renderer;
struct PlatformTexture;
typedef std::string texture_id;
@ -26,9 +26,7 @@ struct Texture {
bool srgb;
bool uploaded;
VkImage image;
VmaAllocation allocation;
VkImageView view;
PlatformTexture *p_texture;
uint32_t descriptor_index;
};
@ -36,9 +34,9 @@ struct TextureManager {
std::unordered_map<texture_id, Texture> textures;
TextureManager();
Texture load(const std::string& path, Renderer &renderer);
Texture load(const std::string& path);
};
inline TextureManager texture_manager;
#endif //V_TEXTURE_H
#endif //TEXTURE_H

View File

@ -2,4 +2,4 @@
// Created by Vicente Ferrari Smith on 14.02.26.
//
#include "../texture_sheet.h"
#include "texture_sheet.h"

View File

@ -7,7 +7,6 @@
#include <string>
#include <glm/glm.hpp>
#include "texture.h"
inline const std::string TEXTURE_SHEETS_PATH = "data/texture_sheets";
@ -26,7 +25,7 @@ struct TextureCell {
int64_t cell_x;
int64_t cell_y;
glm::vec2 st0;
glm::vec2 st0 = {0.0, 0.0};
glm::vec2 st1 = {1.0, 1.0};
};

View File

@ -2,11 +2,12 @@
// Created by Vicente Ferrari Smith on 13.02.26.
//
#include "../Grpahics.h"
#include "renderer.h"
#include "../graphics.h"
#include <print>
#include "init.h"
#include "vulkan.h"
#include "../sprite.h"
#include <vma/vk_mem_alloc.h>
#include <slang/slang.h>
@ -152,7 +153,7 @@ void Renderer::create_pipeline_layout() {
vkCreatePipelineLayout(device, &plci, nullptr, &pipelineLayout);
}
void Renderer::createFrameResources() {
void Renderer::create_frame_resources() {
const VkSemaphoreCreateInfo seci{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
@ -352,91 +353,6 @@ void Renderer::update_bindless_slot(uint32_t slot, VkImageView view, VkSampler s
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
}
void Renderer::upload_texture(
const int w,
const int h,
const void* pixels,
VkImage *image,
VmaAllocation *allocation,
VkImageView *view,
uint32_t *descriptor_index)
{
VkDeviceSize imageSize = w * h * 4;
// --- 1. Create Staging Buffer (CPU Visible) ---
VkBuffer stagingBuffer;
VmaAllocation stagingAlloc;
VkBufferCreateInfo stagingBufferInfo = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
stagingBufferInfo.size = imageSize;
stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo stagingAllocCreateInfo = {
.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
.usage = VMA_MEMORY_USAGE_AUTO,
};
VmaAllocationInfo stagingResultInfo;
vmaCreateBuffer(allocator, &stagingBufferInfo, &stagingAllocCreateInfo, &stagingBuffer, &stagingAlloc, &stagingResultInfo);
// Copy raw pixels into the mapped memory provided by VMA
memcpy(stagingResultInfo.pMappedData, pixels, imageSize);
// --- 2. Create GPU Image (Device Local / Tiled) ---
VkExtent3D imageExtent = { (uint32_t) w, (uint32_t) h, 1 };
VkImageCreateInfo imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = VK_FORMAT_R8G8B8A8_UNORM,
.extent = imageExtent,
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
};
VmaAllocationCreateInfo imageAllocCreateInfo = {
.usage = VMA_MEMORY_USAGE_AUTO,
.priority = 1.0f,
};
vmaCreateImage(allocator, &imageInfo, &imageAllocCreateInfo, image, allocation, nullptr);
// --- 3. The Transfer ---
immediate_submit([&](VkCommandBuffer cmd) {
// Transition image from UNDEFINED to TRANSFER_DST
transition_image_layout(cmd, *image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
VkBufferImageCopy copyRegion = {};
copyRegion.bufferOffset = 0;
copyRegion.bufferRowLength = 0;
copyRegion.bufferImageHeight = 0;
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copyRegion.imageSubresource.mipLevel = 0;
copyRegion.imageSubresource.baseArrayLayer = 0;
copyRegion.imageSubresource.layerCount = 1;
copyRegion.imageExtent = imageExtent;
vkCmdCopyBufferToImage(cmd, stagingBuffer, *image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion);
// Transition image from TRANSFER_DST to SHADER_READ_ONLY
transition_image_layout(cmd, *image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
});
// Clean up temporary staging resources
vmaDestroyBuffer(allocator, stagingBuffer, stagingAlloc);
// --- 4. Finalize Handles ---
*view = create_image_view(*image, imageInfo.format);
// Register in your Bindless Array (Set 0, Binding 0, Index N)
*descriptor_index = nextTextureSlot++;
update_bindless_slot(*descriptor_index, *view, defaultSampler);
}
void Renderer::immediate_submit(std::function<void(VkCommandBuffer)>&& func) const {
VkCommandBufferAllocateInfo allocInfo{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
allocInfo.commandPool = frames[currentFrame].commandPool; // Use a pool created with VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT

View File

@ -5,9 +5,13 @@
#ifndef V_RENDERER_H
#define V_RENDERER_H
#include "init.h"
#include "vulkan.h"
#include <volk/volk.h>
#ifdef __EMSCRIPTEN__
#include <GLFW/emscripten_glfw3.h>
#else
#include <GLFW/glfw3.h>
#endif
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#define GLM_ENABLE_EXPERIMENTAL
@ -16,14 +20,10 @@
#include "glm/gtx/string_cast.hpp"
#include <vma/vk_mem_alloc.h>
#include "../sprite.h"
#include "texture.h"
#include "../texture.h"
#include <misc.h>
#include <array>
#include <span>
#include <slang/slang.h>
#include <slang/slang-com-ptr.h>
inline Slang::ComPtr<slang::IGlobalSession> slangGlobalSession;
enum class PROJECTION_TYPE : uint8_t {
NONE,
@ -208,16 +208,6 @@ struct Renderer {
void create_descriptor_pool();
void update_bindless_slot(uint32_t slot, VkImageView view, VkSampler sampler) const;
// Returns the resource info so the Manager can store it
void upload_texture(
int w,
int h,
const void* pixels,
VkImage *image,
VmaAllocation *allocation,
VkImageView *view,
uint32_t *descriptor_index);
template <typename T>
VkPipeline create_graphics_pipeline(
VkDevice device,

View File

@ -7,7 +7,7 @@
#define VMA_IMPLEMENTATION
#include <vma/vk_mem_alloc.h>
#include "init.h"
#include "vulkan.h"
#include <print>
#include <vector>
@ -39,6 +39,88 @@ std::vector<VkImage> images;
std::vector<VkImageView> imageViews;
std::vector<VkImageLayout> imageLayouts;
void upload_texture(
const int w,
const int h,
const void* pixels,
Texture *texture)
{
VkDeviceSize imageSize = w * h * 4;
// --- 1. Create Staging Buffer (CPU Visible) ---
VkBuffer stagingBuffer;
VmaAllocation stagingAlloc;
VkBufferCreateInfo stagingBufferInfo = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
stagingBufferInfo.size = imageSize;
stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo stagingAllocCreateInfo = {
.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
.usage = VMA_MEMORY_USAGE_AUTO,
};
VmaAllocationInfo stagingResultInfo;
vmaCreateBuffer(allocator, &stagingBufferInfo, &stagingAllocCreateInfo, &stagingBuffer, &stagingAlloc, &stagingResultInfo);
// Copy raw pixels into the mapped memory provided by VMA
memcpy(stagingResultInfo.pMappedData, pixels, imageSize);
// --- 2. Create GPU Image (Device Local / Tiled) ---
VkExtent3D imageExtent = { (uint32_t) w, (uint32_t) h, 1 };
VkImageCreateInfo imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = VK_FORMAT_R8G8B8A8_UNORM,
.extent = imageExtent,
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
};
VmaAllocationCreateInfo imageAllocCreateInfo = {
.usage = VMA_MEMORY_USAGE_AUTO,
.priority = 1.0f,
};
vmaCreateImage(allocator, &imageInfo, &imageAllocCreateInfo, image, allocation, nullptr);
// --- 3. The Transfer ---
immediate_submit([&](VkCommandBuffer cmd) {
// Transition image from UNDEFINED to TRANSFER_DST
transition_image_layout(cmd, *image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
VkBufferImageCopy copyRegion = {};
copyRegion.bufferOffset = 0;
copyRegion.bufferRowLength = 0;
copyRegion.bufferImageHeight = 0;
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copyRegion.imageSubresource.mipLevel = 0;
copyRegion.imageSubresource.baseArrayLayer = 0;
copyRegion.imageSubresource.layerCount = 1;
copyRegion.imageExtent = imageExtent;
vkCmdCopyBufferToImage(cmd, stagingBuffer, *image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion);
// Transition image from TRANSFER_DST to SHADER_READ_ONLY
transition_image_layout(cmd, *image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
});
// Clean up temporary staging resources
vmaDestroyBuffer(allocator, stagingBuffer, stagingAlloc);
// --- 4. Finalize Handles ---
*view = create_image_view(*image, imageInfo.format);
// Register in your Bindless Array (Set 0, Binding 0, Index N)
*descriptor_index = nextTextureSlot++;
update_bindless_slot(*descriptor_index, *view, defaultSampler);
}
void graphics_init() {
createInstance(window);
createSurface(window);
@ -46,10 +128,7 @@ void graphics_init() {
createSwapchain(window);
slang::createGlobalSession(slangGlobalSession.writeRef());
Renderer renderer(window);
texture_manager.load("assets/boy.png", renderer);
}
void graphics_deinit() {

View File

@ -2,8 +2,8 @@
// Created by Vicente Ferrari Smith on 12.02.26.
//
#ifndef V_INIT_H
#define V_INIT_H
#ifndef V_VULKAN_H
#define V_VULKAN_H
#include <volk/volk.h>
#include <GLFW/glfw3.h>
@ -22,4 +22,4 @@ void pickPhysicalDevice();
void createDevice();
#endif //V_INIT_H
#endif //V_VULKAN_H

View File

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

View File

@ -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
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

View File

@ -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);

View File

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

View File

@ -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

View File

@ -0,0 +1,9 @@
StructuredBuffer<float> input_buffer;
RWStructuredBuffer<float> output_buffer;
[shader("compute")]
[numthreads(32,1,1)]
void main(uint3 thread_id : SV_DispatchThreadID)
{
output_buffer[thread_id.x] = 2.0 * input_buffer[thread_id.x];
}

View File

@ -0,0 +1,52 @@
#include <metal_stdlib>
#include <vertex_data.h>
using namespace metal;
struct Vertex {
float2 pos;
float2 uv;
};
constant Vertex vertices[6] = {
{{-0.5, 0.5}, {0.0, 1.0}},
{{-0.5, -0.5}, {0.0, 0.0}},
{{ 0.5, 0.5}, {1.0, 1.0}},
{{ 0.5, 0.5}, {1.0, 1.0}},
{{-0.5, -0.5}, {0.0, 0.0}},
{{ 0.5, -0.5}, {1.0, 0.0}},
};
struct VertexOut {
float4 pos [[position]];
float2 uv;
float4 color;
};
vertex VertexOut vertex_main(
uint vertexID [[vertex_id]],
constant vertex_p2_s2_uv2_c4_a1* in,
constant simd::float4x4 *ortho)
{
Vertex v = vertices[vertexID % 6];
VertexOut out;
out.pos = (*ortho) * (float4((v.pos * in[vertexID].scale) + in[vertexID].pos, 0.0, 1.0));
out.uv = v.uv;
out.color = in[vertexID].color;
return out;
}
fragment float4 fragment_main(VertexOut in [[stage_in]], texture2d<float> colorTexture [[texture(0)]]) {
constexpr sampler textureSampler (mag_filter::linear, min_filter::linear);
// Sample the texture to obtain a color
const float4 colorSample = colorTexture.sample(textureSampler, in.uv);
//float2 srgbOut = select(1.292 * in.uv,
// 1.055 * pow(in.uv, 1.0/2.4) - 0.055,
// in.uv > 0.0031308);
return colorSample;
//return float4(srgbOut, 0.0, in.color.a);
}

View File

@ -4,7 +4,6 @@ struct VSInput {
float2 uv;
float4 color;
float alpha;
uint32_t textureID;
uint vertex_index : SV_VertexID;
};
@ -14,10 +13,17 @@ struct VSOutput {
float2 uv;
float4 color;
float alpha;
uint32_t tex_id;
//uint32_t tex_id;
};
Sampler2D textures[];
struct Uniforms {
float4x4 proj;
};
ConstantBuffer<Uniforms> uniforms : register(b1);
Texture2D colorTexture : register(t0);
SamplerState samplerState;
static const float2 square[6] = {
float2(-0.5, -0.5), // Top-left
@ -30,23 +36,23 @@ static const float2 square[6] = {
};
[shader ("vertex")]
VSOutput main(VSInput input, uniform float4x4 proj) {
VSOutput vs_main(VSInput input) {
VSOutput output;
float2 vertex_pos = square[input.vertex_index % 6];
float2 final_pos = (vertex_pos * input.scale) + input.pos;
output.pos = mul(proj, float4(final_pos, 0.0, 1.0));
output.pos = mul(uniforms.proj, float4(final_pos, 0.0, 1.0));
output.uv = input.uv;
output.color = input.color;
output.alpha = input.alpha;
output.tex_id = input.textureID;
//output.tex_id = input.textureID;
return output;
}
[shader("fragment")]
float4 main(VSOutput input) {
return float4(input.color.rgb, input.alpha);
float4 fs_main(VSOutput input) {
float4 texColor = colorTexture.Sample(samplerState, input.uv);
return texColor * float4(input.color.rgb, input.alpha);
}