Compare commits

..

2 Commits

Author SHA1 Message Date
6883d103ff . 2026-04-28 19:46:41 +02:00
a0b7e4c0d8 . 2026-04-28 19:46:32 +02:00
39 changed files with 3412 additions and 312 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

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

14
renderer/graphics.cpp Normal file
View File

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

48
renderer/graphics.h Normal file
View File

@ -0,0 +1,48 @@
//
// Created by Vicente Ferrari Smith on 26.02.26.
//
#ifndef V_GRAPHICS_H
#define V_GRAPHICS_H
#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>
#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

@ -0,0 +1,16 @@
//
// Created by Vicente Ferrari Smith on 02.03.26.
//
#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,47 +0,0 @@
//
// Created by Vicente Ferrari Smith on 12.02.26.
//
#ifndef V_INIT_H
#define V_INIT_H
#include <volk/volk.h>
#include <GLFW/glfw3.h>
#include <vma/vk_mem_alloc.h>
#include <vector>
inline VkInstance instance{};
inline VkPhysicalDevice physicalDevice{};
inline VkDevice device{};
inline VkQueue graphics_queue{};
inline uint32_t queueFamily{};
inline VkSurfaceKHR surface{};
inline VkDebugUtilsMessengerEXT debugMessenger{};
inline VmaAllocator allocator{};
inline constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2;
inline constexpr uint32_t MAX_VERTICES_PER_BATCH = 65536;
inline VkSwapchainKHR swapchain;
inline VkExtent2D swapchain_extent;
inline VkSurfaceFormatKHR swapchain_format{
VK_FORMAT_B8G8R8A8_UNORM,
VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
};
inline std::vector<VkSemaphore> renderFinished;
inline std::vector<VkImage> images;
inline std::vector<VkImageView> imageViews;
inline std::vector<VkImageLayout> imageLayouts;
void createSwapchain(GLFWwindow* window);
int createInstance(GLFWwindow* window);
void createSurface(GLFWwindow* window);
void pickPhysicalDevice();
void createDevice();
#endif //V_INIT_H

View File

@ -0,0 +1,637 @@
/*
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

@ -0,0 +1,266 @@
/*
See LICENSE folder for this sampleâs licensing information.
Abstract:
Header for vector, matrix, and quaternion math utility functions useful for 3D graphics rendering.
*/
#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);

206
renderer/metal/metal.cpp Normal file
View File

@ -0,0 +1,206 @@
//
// Created by Vicente Ferrari Smith on 26.02.26.
//
#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>
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_COCOA
#include <GLFW/glfw3native.h>
#include <objc/message.h>
#include <objc/objc.h>
extern int32_t window_width;
extern int32_t window_height;
Device metal_device{};
MTL::CommandQueue *queue{};
CA::MetalLayer *metal_layer{};
CA::MetalDrawable *metal_drawable{};
Renderer renderer;
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");
}
SEL contentViewSel = sel_registerName("contentView");
id content_view = ((id (*)(id, SEL))objc_msgSend)((id)ns_window, contentViewSel);
SEL setWantsLayerSel = sel_registerName("setWantsLayer:");
((void (*)(id, SEL, bool))objc_msgSend)(content_view, setWantsLayerSel, true);
metal_layer = CA::MetalLayer::layer()->retain();
SEL setLayerSel = sel_registerName("setLayer:");
((void (*)(id, SEL, id))objc_msgSend)(content_view, setLayerSel, (id)metal_layer);
metal_layer->retain();
metal_layer->setDevice(metal_device.device);
metal_layer->setPixelFormat(MTL::PixelFormatRGBA16Float);
metal_layer->setFramebufferOnly(true);
metal_layer->setDrawableSize(CGSizeMake(800, 600));
CGColorSpaceRef p3Space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
metal_layer->setColorspace(p3Space);
}
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)
{
NS::Error *error = nullptr;
MTL::Library *library = nullptr;
auto ends_with = [](const std::string& s, const char* suf) -> bool {
const size_t n = std::strlen(suf);
return s.size() >= n && s.compare(s.size() - n, n, suf) == 0;
};
if (ends_with(shader_path, ".metal")) {
// Compile from source at runtime
std::ifstream file(shader_path, std::ios::in | std::ios::binary);
if (!file.is_open()) {
throw std::runtime_error("Failed to open .metal source file");
}
std::string src;
file.seekg(0, std::ios::end);
src.resize(static_cast<size_t>(file.tellg()));
file.seekg(0, std::ios::beg);
file.read(src.data(), static_cast<std::streamsize>(src.size()));
file.close();
NS::String* source = NS::String::string(src.c_str(), NS::UTF8StringEncoding);
MTL::CompileOptions* opts = MTL::CompileOptions::alloc()->init();
library = metal_device.device->newLibrary(source, opts, &error);
opts->release();
} else {
// Load a precompiled metallib from file path
NS::String *nsPath = NS::String::string(shader_path.c_str(), NS::UTF8StringEncoding);
library = metal_device.device->newLibrary(nsPath, &error);
}
if (error || library == nullptr) {
if (error) {
// Extract the actual compiler error message
const char* errorMessage = error->localizedDescription()->utf8String();
std::string detailedError = "Metal Library Error: ";
detailedError += errorMessage;
// It is good practice to release the error object if it exists
error->release();
throw std::runtime_error(detailedError);
}
throw std::runtime_error("Failed to create Metal library (Unknown error)");
}
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);
if (*vertex_shader == nullptr || *fragment_shader == nullptr) {
throw std::runtime_error("Failed to create Metal shader functions");
}
library->release();
}
void create_command_queue() {
queue = metal_device.device->newCommandQueue();
}
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);
}

34
renderer/metal/metal.h Normal file
View File

@ -0,0 +1,34 @@
//
// Created by Vicente Ferrari Smith on 26.02.26.
//
#ifndef V_METAL_H
#define V_METAL_H
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_COCOA
#import <GLFW/glfw3native.h>
#include <Metal/Metal.hpp>
#include <QuartzCore/CAMetalLayer.hpp>
#include <QuartzCore/QuartzCore.hpp>
struct Device {
MTL::Device *device;
};
struct PlatformTexture {
MTL::Texture *texture;
};
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

379
renderer/metal/renderer.cpp Normal file
View File

@ -0,0 +1,379 @@
//
// Created by Vicente Ferrari Smith on 13.02.26.
//
#include <print>
#include <slang.h>
#include <slang-com-ptr.h>
#include "../graphics.h"
#include "../sprite.h"
#include "renderer.h"
#include "metal.h"
#include "vertex_data.h"
extern int32_t window_width;
extern int32_t window_height;
extern Device metal_device;
extern MTL::CommandQueue *queue;
extern CA::MetalLayer *metal_layer;
extern CA::MetalDrawable *metal_drawable;
bool SortKey::operator<(const SortKey& b) const {
if (depth != b.depth) return depth < b.depth;
if (pipeline != b.pipeline) return pipeline < b.pipeline;
return materialID < b.materialID;
}
Renderer::Renderer(GLFWwindow *window) {
create_render_pipeline();
frame_semaphore = dispatch_semaphore_create(kMaxFramesInFlight);
current_frame = 0;
for (Frame &frame : frames) {
frame.vertex_buffer = metal_device.device->newBuffer(
4 * 1024 * 1024,
MTL::ResourceStorageModeShared
);
frame.uniform_buffer = metal_device.device->newBuffer(
sizeof(simd::float4x4),
MTL::ResourceStorageModeShared
);
}
// colored_quad_pipeline = create_graphics_pipeline<vertex_p2_s2_st2_col4_a1_u32>(
// device,
// pipelineLayout,
// swapchain_format.format,
// VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
// true
// );
// create_default_sampler();
// create_descriptor_pool();
// createFrameResources();
}
void Renderer::begin_frame() {
commands.clear();
}
void Renderer::submit_quad(simd::float2 pos, simd::float2 scale) {
RenderCommand cmd {};
cmd.pipeline = PipelineType::ColoredQuad;
cmd.key = {
(uint16_t) pos.y,
0,
(uint8_t) PipelineType::ColoredQuad
};
cmd.colored_quad = {
.pos = pos,
.scale = scale,
.colour = {0, 1, 1, 1},
};
commands.push_back(cmd);
}
void Renderer::submit_sprite(simd::float2 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);
}
MTL::RenderPipelineState *Renderer::get_pipeline(PipelineType type) const {
switch (type) {
case PipelineType::TexturedQuad: return textured_quad_pipeline;
case PipelineType::ColoredQuad: return colored_quad_pipeline;
case PipelineType::Line: return line_pipeline;
default: return {};
}
}
// void Renderer::bind_material(VkCommandBuffer cmd, uint16_t materialID) {
// // In a real app, you'd have an array/map: std::vector<VkDescriptorSet> textureSets;
// VkDescriptorSet set = textureSets[materialID];
//
// vkCmdBindDescriptorSets(
// cmd,
// VK_PIPELINE_BIND_POINT_GRAPHICS,
// pipelineLayout, // Our shared layout
// 0, // Starting at Set 0
// 1, // Binding 1 set
// &set,
// 0, nullptr
// );
// }
void Renderer::end_frame(GLFWwindow *window) {
send_render_command(window);
}
void Renderer::create_render_pipeline() {
MTL::Function *vertex_shader{};
MTL::Function *fragment_shader{};
// load_metal_shader(
// "shaders/shaders.metallib",
// "vertex_main",
// "fragment_main",
// &vertex_shader,
// &fragment_shader);
// 3. Load the Module
Slang::ComPtr<slang::IBlob> diagnostics;
Slang::ComPtr<slang::IModule> module(slangSession->loadModule("shader", diagnostics.writeRef()));
if(diagnostics) {
fprintf(stderr, "%s\n", (const char*) diagnostics->getBufferPointer());
}
Slang::ComPtr<slang::IEntryPoint> vertEntry;
Slang::ComPtr<slang::IEntryPoint> fragEntry;
module->findEntryPointByName("vs_main", vertEntry.writeRef());
module->findEntryPointByName("fs_main", fragEntry.writeRef());
slang::IComponentType* components[] = { module, vertEntry, fragEntry };
Slang::ComPtr<slang::IComponentType> program;
slangSession->createCompositeComponentType(components, 3, program.writeRef());
Slang::ComPtr<slang::IBlob> code;
Slang::ComPtr<slang::IBlob> diagnosticBlob;
program->getTargetCode(
0,
code.writeRef(),
diagnostics.writeRef()
);
if (diagnostics)
std::println("{}", (const char*)diagnostics->getBufferPointer());
std::println("Generated MSL:\n{}", (const char*)code->getBufferPointer());
NS::Error *error = nullptr;
MTL::Library *library = nullptr;
NS::String* source = NS::String::string((const char *) code->getBufferPointer(), NS::UTF8StringEncoding);
MTL::CompileOptions* opts = MTL::CompileOptions::alloc()->init();
library = metal_device.device->newLibrary(source, opts, &error);
opts->release();
if (!library) {
if (error) {
std::println("Metal library compile error:");
std::println("Domain: {}",
error->domain()->utf8String());
std::println("Code: {}",
error->code());
std::println("Description:\n{}",
error->localizedDescription()->utf8String());
}
std::abort();
}
NS::String *vname = NS::String::string("vs_main", NS::UTF8StringEncoding);
NS::String *fname = NS::String::string("fs_main", NS::UTF8StringEncoding);
vertex_shader = library->newFunction(vname);
fragment_shader = library->newFunction(fname);
if (!vertex_shader) {
std::println("vs_main not found in generated MSL");
std::abort();
}
if (!fragment_shader) {
std::println("fs_main not found in generated MSL");
std::abort();
}
library->release();
MTL::VertexDescriptor *vd = vertex_p2_s2_uv2_c4_a1::vertexDescriptor();
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);
renderPipelineDescriptor->setVertexDescriptor(vd);
assert(renderPipelineDescriptor);
const MTL::PixelFormat pixel_format = metal_layer->pixelFormat();
renderPipelineDescriptor->colorAttachments()->object(0)->setPixelFormat(pixel_format);
textured_quad_pipeline = metal_device.device->newRenderPipelineState(renderPipelineDescriptor, &error);
if (!textured_quad_pipeline) {
if (error) {
std::println("Pipeline error: {}",
error->localizedDescription()->utf8String());
}
std::abort();
}
renderPipelineDescriptor->release();
vd->release();
}
float4x4 make_ortho(float left, float right, float bottom, float top, float near, float far) {
float sx = 2.0f / (right - left);
float sy = 2.0f / (top - bottom);
float sz = 1.0f / (far - near);
float tx = -(right + left) / (right - left);
float ty = -(bottom + top) / (top - bottom);
float tz = -near / (far - near);
return float4x4(
float4{sx, 0.0f, 0.0f, 0.0f},
float4{0.0f, sy, 0.0f, 0.0f},
float4{0.0f, 0.0f, sz, 0.0f},
float4{tx, ty, tz, 1.0f}
);
}
void Renderer::encode_render_command(GLFWwindow *window, MTL::RenderCommandEncoder *render_command_encoder) {
std::vector<vertex_p2_s2_uv2_c4_a1> vertices;
for (auto& cmd : commands) {
switch (cmd.pipeline) {
case PipelineType::ColoredQuad: {
const auto &q = cmd.colored_quad;
vertex_p2_s2_uv2_c4_a1 vTL = { q.pos, q.scale, {}, q.colour };
vertex_p2_s2_uv2_c4_a1 vTR = { q.pos, q.scale, {}, q.colour };
vertex_p2_s2_uv2_c4_a1 vBL = { q.pos, q.scale, {}, q.colour };
vertex_p2_s2_uv2_c4_a1 vBR = { q.pos, q.scale, {}, q.colour };
vertices.push_back(vTL);
vertices.push_back(vBL);
vertices.push_back(vTR);
vertices.push_back(vTR);
vertices.push_back(vBL);
vertices.push_back(vBR);
break;
}
case PipelineType::TexturedQuad: {
const auto &q = cmd.textured_quad;
vertex_p2_s2_uv2_c4_a1 vTL = { q.pos, q.scale, q.uv0, q.colour, 1.0 };
vertex_p2_s2_uv2_c4_a1 vTR = { q.pos, q.scale, {q.uv1.x, q.uv0.y}, q.colour, 1.0 };
vertex_p2_s2_uv2_c4_a1 vBL = { q.pos, q.scale, {q.uv0.x, q.uv1.y}, q.colour, 1.0 };
vertex_p2_s2_uv2_c4_a1 vBR = { q.pos, q.scale, q.uv1, q.colour, 1.0};
vertices.push_back(vTL);
vertices.push_back(vBL);
vertices.push_back(vTR);
vertices.push_back(vTR);
vertices.push_back(vBL);
vertices.push_back(vBR);
break;
}
default:
break;
}
}
const Frame &frame = frames[current_frame];
memcpy(frame.vertex_buffer->contents(), vertices.data(), vertices.size() * sizeof(vertex_p2_s2_uv2_c4_a1));
MTL::SamplerDescriptor* sampler_desc = MTL::SamplerDescriptor::alloc()->init();
sampler_desc->setMinFilter(MTL::SamplerMinMagFilterLinear);
sampler_desc->setMagFilter(MTL::SamplerMinMagFilterLinear);
MTL::SamplerState* sampler = metal_device.device->newSamplerState(sampler_desc);
int width, height;
glfwGetFramebufferSize(window, &width, &height);
simd::float4x4 ortho = make_ortho(0.0, window_width, window_height, 0.0, 1.0, 0.0);
auto tm = simd::transpose(ortho);
memcpy(frame.uniform_buffer->contents(), &tm, sizeof(ortho));
render_command_encoder->setRenderPipelineState(textured_quad_pipeline);
render_command_encoder->setVertexBuffer(frame.vertex_buffer, 0, 0);
render_command_encoder->setVertexBuffer(frame.uniform_buffer, 0, 1);
render_command_encoder->setFragmentSamplerState(sampler, 0);
MTL::PrimitiveType type_triangle = MTL::PrimitiveTypeTriangle;
NS::UInteger vertexStart = 0;
NS::UInteger vertexCount = vertices.size();
const Texture &texture = texture_manager.textures["assets/boy.png"];
render_command_encoder->setFragmentTexture(texture.p_texture->texture, 0);
render_command_encoder->drawPrimitives(type_triangle, vertexStart, vertexCount);
sampler_desc->release();
}
void Renderer::send_render_command(GLFWwindow *window) {
dispatch_semaphore_wait(frame_semaphore, DISPATCH_TIME_FOREVER);
command_buffer = queue->commandBuffer();
dispatch_semaphore_t sem = frame_semaphore;
command_buffer->addCompletedHandler([sem](MTL::CommandBuffer* pBuf) {
dispatch_semaphore_signal(sem);
});
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 = command_buffer->renderCommandEncoder(renderPassDescriptor);
encode_render_command(window, renderCommandEncoder);
renderCommandEncoder->endEncoding();
command_buffer->presentDrawable(metal_drawable);
command_buffer->commit();
command_buffer->waitUntilCompleted();
current_frame = (current_frame + 1) % kMaxFramesInFlight;
renderPassDescriptor->release();
}

185
renderer/metal/renderer.h Normal file
View File

@ -0,0 +1,185 @@
//
// Created by Vicente Ferrari Smith on 13.02.26.
//
#ifndef V_RENDERER_H
#define V_RENDERER_H
#include "metal.h"
#include <GLFW/glfw3.h>
#include "../sprite.h"
#include <misc.h>
#include <simd/simd.h>
static const int kMaxFramesInFlight = 3;
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 {
simd::float2 pos;
simd::float2 scale;
simd::float2 uv0;
simd::float2 uv1;
simd::float4 colour;
MTL::Texture *texture;
};
struct ColoredQuadCmd {
simd::float2 pos;
simd::float2 scale;
simd::float4 colour;
};
struct LineCmd {
simd::float2 start;
simd::float2 end;
simd::float4 color;
};
// struct TextCmd {
// Font* font;
// std::string text;
// glm::vec2 position;
// glm::vec4 color;
// };
//struct ChunkCmd {
// VkBuffer vertexBuffer;
// VkBuffer indexBuffer;
// uint32_t indexCount;
//};
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 AllocatedBuffer {
// VkBuffer buffer;
// VmaAllocation allocation;
// VmaAllocationInfo info;
// };
// struct GPUMeshBuffers {
// AllocatedBuffer indexBuffer;
// AllocatedBuffer vertexBuffer;
// VkDeviceAddress vertexBufferAddress;
// };
struct Renderer {
std::vector<RenderCommand> commands{};
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 {
MTL::Buffer *vertex_buffer{};
MTL::Buffer *uniform_buffer{};
};
Frame frames[kMaxFramesInFlight];
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(GLFWwindow *window);
void flush();
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_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

@ -0,0 +1,57 @@
//
// Created by Vicente Ferrari Smith on 27.02.26.
//
#pragma once
#include <simd/simd.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,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,16 +1,15 @@
//
// Created by Vicente Ferrari Smith on 14.02.26.
// Created by Vicente Ferrari Smith on 01.03.26.
//
#include "texture.h"
#include <stb_image.h>
#include "renderer.h"
#include "graphics.h"
TextureManager::TextureManager() {
}
Texture TextureManager::load(const std::string& path, Renderer &renderer) {
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];
@ -23,7 +22,7 @@ Texture TextureManager::load(const std::string& path, Renderer &renderer) {
res.height = h;
res.channels = STBI_rgb_alpha;
res.srgb = true;
renderer.upload_texture(w, h, data, &res.image, &res.allocation, &res.view, &res.descriptor_index);
upload_texture(w, h, data, &res);
stbi_image_free(data);

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

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

@ -3,11 +3,12 @@
//
#include "renderer.h"
#include "../graphics.h"
#include <print>
#include "init.h"
#include "sprite.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
@ -15,15 +19,11 @@
#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 "../sprite.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

@ -2,10 +2,147 @@
// Created by Vicente Ferrari Smith on 12.02.26.
//
#include "init.h"
#define VOLK_IMPLEMENTATION
#include <Volk/volk.h>
#define VMA_IMPLEMENTATION
#include <vma/vk_mem_alloc.h>
#include "vulkan.h"
#include <print>
#include <vector>
#include "../graphics.h"
VkInstance instance{};
VkPhysicalDevice physicalDevice{};
VkDevice device{};
VkQueue graphics_queue{};
uint32_t queueFamily{};
VkSurfaceKHR surface{};
VkDebugUtilsMessengerEXT debugMessenger{};
VmaAllocator allocator{};
constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2;
constexpr uint32_t MAX_VERTICES_PER_BATCH = 65536;
VkSwapchainKHR swapchain;
VkExtent2D swapchain_extent;
VkSurfaceFormatKHR swapchain_format{
VK_FORMAT_B8G8R8A8_UNORM,
VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
};
std::vector<VkSemaphore> renderFinished;
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);
createDevice();
createSwapchain(window);
Renderer renderer(window);
}
void graphics_deinit() {
vkDeviceWaitIdle(device);
}
void begin_frame() {
}
void end_frame() {
}
VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT severity,
VkDebugUtilsMessageTypeFlagsEXT type,

25
renderer/vulkan/vulkan.h Normal file
View File

@ -0,0 +1,25 @@
//
// Created by Vicente Ferrari Smith on 12.02.26.
//
#ifndef V_VULKAN_H
#define V_VULKAN_H
#include <volk/volk.h>
#include <GLFW/glfw3.h>
#include <vma/vk_mem_alloc.h>
#include <vector>
struct Device {
VkDevice device;
};
void createSwapchain(GLFWwindow* window);
int createInstance(GLFWwindow* window);
void createSurface(GLFWwindow* window);
void pickPhysicalDevice();
void createDevice();
#endif //V_VULKAN_H

View File

@ -0,0 +1,280 @@
//
// Created by Vicente Ferrari Smith on 06.03.26.
//
#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
}

106
renderer/webgpu/renderer.h Normal file
View File

@ -0,0 +1,106 @@
//
// Created by Vicente Ferrari Smith on 06.03.26.
//
#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"
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

@ -0,0 +1,79 @@
//
// Created by Vicente Ferrari Smith on 06.03.26.
//
// Copyright 2025 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#if !defined(EMSCRIPTEN)
#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

View File

@ -0,0 +1,40 @@
//
// Created by Vicente Ferrari Smith on 06.03.26.
//
// Copyright 2025 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#if !defined(EMSCRIPTEN)
#error "utils_emscripten.cpp: This file requires EMSCRIPTEN to be defined."
#endif // !defined(EMSCRIPTEN)
#include "GLFW/glfw3.h"
#include "webgpu/webgpu_glfw.h"
WGPU_GLFW_EXPORT WGPUSurface wgpuGlfwCreateSurfaceForWindow(const WGPUInstance instance,
GLFWwindow* window);

427
renderer/webgpu/webgpu.cpp Normal file
View File

@ -0,0 +1,427 @@
//
// Created by Vicente Ferrari Smith on 06.03.26.
//
#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);
}

38
renderer/webgpu/webgpu.h Normal file
View File

@ -0,0 +1,38 @@
//
// Created by Vicente Ferrari Smith on 06.03.26.
//
#ifndef V_WEBGPU_H
#define V_WEBGPU_H
#include <string_view>
#include <webgpu/webgpu.h>
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

9
shaders/compute.slang Normal file
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];
}

13
shaders/compute.wgsl Normal file
View File

@ -0,0 +1,13 @@
@binding(1) @group(0) var<storage, read_write> output_buffer_0 : array<f32>;
@binding(0) @group(0) var<storage, read> input_buffer_0 : array<f32>;
@compute
@workgroup_size(32, 1, 1)
fn main(@builtin(global_invocation_id) thread_id_0 : vec3<u32>)
{
var _S1 : u32 = thread_id_0.x;
output_buffer_0[_S1] = 2.0f * input_buffer_0[_S1];
return;
}

BIN
shaders/shader.air Normal file

Binary file not shown.

52
shaders/shader.metal Normal file
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);
}

63
shaders/shader.wgsl Normal file
View File

@ -0,0 +1,63 @@
struct _MatrixStorage_float4x4_ColMajorstd140_0
{
@align(16) data_0 : array<vec4<f32>, i32(4)>,
};
struct Uniforms_std140_0
{
@align(16) proj_0 : _MatrixStorage_float4x4_ColMajorstd140_0,
};
@binding(1) @group(0) var<uniform> uniforms_0 : Uniforms_std140_0;
@binding(0) @group(0) var colorTexture_0 : texture_2d<f32>;
@binding(2) @group(0) var samplerState_0 : sampler;
const square_0 : array<vec2<f32>, i32(6)> = array<vec2<f32>, i32(6)>( vec2<f32>(-0.5f, -0.5f), vec2<f32>(-0.5f, 0.5f), vec2<f32>(0.5f, -0.5f), vec2<f32>(0.5f, -0.5f), vec2<f32>(-0.5f, 0.5f), vec2<f32>(0.5f, 0.5f) );
struct VSOutput_0
{
@builtin(position) pos_0 : vec4<f32>,
uv_0 : vec2<f32>,
color_0 : vec4<f32>,
alpha_0 : f32,
};
struct vertexInput_0
{
@location(0) pos_1 : vec2<f32>,
@location(1) scale_0 : vec2<f32>,
@location(2) uv_1 : vec2<f32>,
@location(3) color_1 : vec4<f32>,
@location(4) alpha_1 : f32,
};
@vertex
fn vs_main( _S1 : vertexInput_0, @builtin(vertex_index) vertex_index_0 : u32) -> VSOutput_0
{
var output_0 : VSOutput_0;
output_0.pos_0 = (((vec4<f32>(square_0[vertex_index_0 % u32(6)] * _S1.scale_0 + _S1.pos_1, 0.0f, 1.0f)) * (mat4x4<f32>(uniforms_0.proj_0.data_0[i32(0)][i32(0)], uniforms_0.proj_0.data_0[i32(1)][i32(0)], uniforms_0.proj_0.data_0[i32(2)][i32(0)], uniforms_0.proj_0.data_0[i32(3)][i32(0)], uniforms_0.proj_0.data_0[i32(0)][i32(1)], uniforms_0.proj_0.data_0[i32(1)][i32(1)], uniforms_0.proj_0.data_0[i32(2)][i32(1)], uniforms_0.proj_0.data_0[i32(3)][i32(1)], uniforms_0.proj_0.data_0[i32(0)][i32(2)], uniforms_0.proj_0.data_0[i32(1)][i32(2)], uniforms_0.proj_0.data_0[i32(2)][i32(2)], uniforms_0.proj_0.data_0[i32(3)][i32(2)], uniforms_0.proj_0.data_0[i32(0)][i32(3)], uniforms_0.proj_0.data_0[i32(1)][i32(3)], uniforms_0.proj_0.data_0[i32(2)][i32(3)], uniforms_0.proj_0.data_0[i32(3)][i32(3)]))));
output_0.uv_0 = _S1.uv_1;
output_0.color_0 = _S1.color_1;
output_0.alpha_0 = _S1.alpha_1;
return output_0;
}
struct pixelOutput_0
{
@location(0) output_1 : vec4<f32>,
};
struct pixelInput_0
{
@location(0) uv_2 : vec2<f32>,
@location(1) color_2 : vec4<f32>,
@location(2) alpha_2 : f32,
};
@fragment
fn fs_main( _S2 : pixelInput_0, @builtin(position) pos_2 : vec4<f32>) -> pixelOutput_0
{
var _S3 : pixelOutput_0 = pixelOutput_0( (textureSample((colorTexture_0), (samplerState_0), (_S2.uv_2))) * vec4<f32>(_S2.color_2.xyz, _S2.alpha_2) );
return _S3;
}

BIN
shaders/shaders.metallib Normal file

Binary file not shown.