Compare commits
2 Commits
a015aa00ef
...
6883d103ff
| Author | SHA1 | Date | |
|---|---|---|---|
| 6883d103ff | |||
| a0b7e4c0d8 |
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@ -4,4 +4,5 @@
|
|||||||
<option name="pythonIntegrationState" value="YES" />
|
<option name="pythonIntegrationState" value="YES" />
|
||||||
</component>
|
</component>
|
||||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||||
|
<component name="WestSettings"><![CDATA[{}]]></component>
|
||||||
</project>
|
</project>
|
||||||
209
CMakeLists.txt
209
CMakeLists.txt
@ -5,38 +5,170 @@ set(CMAKE_CXX_STANDARD 23)
|
|||||||
|
|
||||||
include(FetchContent)
|
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")
|
target_include_directories(v
|
||||||
find_path(SLANG_INCLUDE_DIR NAMES slang/slang.h HINTS "$ENV{VULKAN_SDK}/include")
|
PRIVATE
|
||||||
find_file(SLANG_DLL NAMES slang.dll HINTS "$ENV{VULKAN_SDK}/bin")
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
if(SLANG_LIB AND SLANG_INCLUDE_DIR)
|
target_link_libraries(v PRIVATE glfw glm::glm stb)
|
||||||
add_library(slang_sdk SHARED IMPORTED)
|
|
||||||
set_target_properties(slang_sdk PROPERTIES
|
if (EMSCRIPTEN)
|
||||||
IMPORTED_IMPLIB "${SLANG_LIB}"
|
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||||
INTERFACE_INCLUDE_DIRECTORIES "${SLANG_INCLUDE_DIR}"
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND WGSL_OUTPUTS ${OUTPUT_PATH})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# 4. Create a single target that tracks all these outputs
|
||||||
|
add_custom_target(CompileShaders ALL DEPENDS ${WGSL_OUTPUTS})
|
||||||
|
|
||||||
|
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
# If we found the DLL, set it as the location;
|
target_link_libraries(v PRIVATE
|
||||||
# otherwise, on non-Windows systems, SLANG_LIB is the location.
|
"-framework Metal"
|
||||||
if(WIN32 AND SLANG_DLL)
|
"-framework MetalKit"
|
||||||
set_target_properties(slang_sdk PROPERTIES IMPORTED_LOCATION "${SLANG_DLL}")
|
"-framework AppKit"
|
||||||
else()
|
"-framework Foundation"
|
||||||
set_target_properties(slang_sdk PROPERTIES IMPORTED_LOCATION "${SLANG_LIB}")
|
"-framework QuartzCore"
|
||||||
endif()
|
slang::slang
|
||||||
|
)
|
||||||
|
|
||||||
message(STATUS "Slang found via VULKAN_SDK: ${SLANG_LIB}")
|
target_sources(v PRIVATE
|
||||||
else()
|
renderer/metal/metal.cpp
|
||||||
message(FATAL_ERROR "VULKAN_SDK env var is set, but Slang wasn't found inside it!")
|
renderer/metal/metal.h
|
||||||
endif()
|
renderer/metal/renderer.cpp
|
||||||
|
renderer/metal/renderer.h
|
||||||
|
)
|
||||||
|
|
||||||
FetchContent_Declare(
|
#[[ #shaders
|
||||||
glfw
|
|
||||||
GIT_REPOSITORY https://github.com/glfw/glfw.git
|
set(SHADER_DIR ${CMAKE_SOURCE_DIR}/shaders)
|
||||||
GIT_TAG 232164f62b0edbf667cba37c91bab92ffbb020d0
|
set(SHADER_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/renderer/metal)
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(glfw)
|
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(
|
FetchContent_Declare(
|
||||||
glm
|
glm
|
||||||
@ -56,27 +188,6 @@ FetchContent_MakeAvailable(stb)
|
|||||||
add_library(stb INTERFACE)
|
add_library(stb INTERFACE)
|
||||||
target_include_directories(stb INTERFACE ${stb_SOURCE_DIR})
|
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_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/shaders")
|
||||||
set(SHADER_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders")
|
set(SHADER_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders")
|
||||||
file(GLOB_RECURSE SHADER_SOURCES
|
file(GLOB_RECURSE SHADER_SOURCES
|
||||||
@ -85,7 +196,7 @@ file(GLOB_RECURSE SHADER_SOURCES
|
|||||||
"${SHADER_SOURCE_DIR}/*.comp"
|
"${SHADER_SOURCE_DIR}/*.comp"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SPIRV_BINARY_FILES "")
|
#[[set(SPIRV_BINARY_FILES "")
|
||||||
|
|
||||||
foreach(SHADER ${SHADER_SOURCES})
|
foreach(SHADER ${SHADER_SOURCES})
|
||||||
|
|
||||||
@ -104,4 +215,4 @@ endforeach()
|
|||||||
|
|
||||||
add_custom_target(compile_shaders ALL DEPENDS ${SPIRV_BINARY_FILES})
|
add_custom_target(compile_shaders ALL DEPENDS ${SPIRV_BINARY_FILES})
|
||||||
|
|
||||||
add_dependencies(v compile_shaders)
|
add_dependencies(v compile_shaders)]]
|
||||||
|
|||||||
151
main.cpp
151
main.cpp
@ -2,16 +2,17 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#define VOLK_IMPLEMENTATION
|
#ifdef __EMSCRIPTEN__
|
||||||
#include <Volk/volk.h>
|
#include <GLFW/emscripten_glfw3.h>
|
||||||
#define VMA_IMPLEMENTATION
|
#include <emscripten.h>
|
||||||
#include <vma/vk_mem_alloc.h>
|
#else
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "renderer/init.h"
|
#include "renderer/graphics.h"
|
||||||
#include "renderer/renderer.h"
|
#include "renderer/texture_sheet.h"
|
||||||
#include "renderer/texture.h"
|
|
||||||
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
#include <stb_image.h>
|
#include <stb_image.h>
|
||||||
@ -24,14 +25,25 @@ int32_t window_height = 480;
|
|||||||
uint64_t t = 0;
|
uint64_t t = 0;
|
||||||
uint64_t accumulator = 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())
|
if (!glfwInit())
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
dt = (uint64_t) (1.0 / 60.0 * glfwGetTimerFrequency());
|
|
||||||
|
|
||||||
std::println("Hello, Sailor!");
|
std::println("Hello, Sailor!");
|
||||||
|
|
||||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||||
@ -41,64 +53,67 @@ int main() {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
createInstance(window);
|
graphics_init(window);
|
||||||
createSurface(window);
|
|
||||||
createDevice();
|
|
||||||
|
|
||||||
createSwapchain(window);
|
texture_manager.load("assets/boy.png");
|
||||||
|
|
||||||
slang::createGlobalSession(slangGlobalSession.writeRef());
|
return 0;
|
||||||
|
}
|
||||||
Renderer renderer(window);
|
|
||||||
|
bool is_running() {
|
||||||
texture_manager.load("assets/boy.png", renderer);
|
return !glfwWindowShouldClose(window);
|
||||||
|
}
|
||||||
uint64_t current_time = glfwGetTimerValue();
|
|
||||||
|
void main_loop(void *data) {
|
||||||
while (!glfwWindowShouldClose(window)) {
|
uint64_t new_time = glfwGetTimerValue();
|
||||||
|
uint64_t frame_time = new_time - current_time;
|
||||||
uint64_t new_time = glfwGetTimerValue();
|
current_time = new_time;
|
||||||
uint64_t frame_time = new_time - current_time;
|
|
||||||
current_time = new_time;
|
accumulator += frame_time;
|
||||||
|
|
||||||
accumulator += frame_time;
|
glfwPollEvents();
|
||||||
|
|
||||||
glfwPollEvents();
|
uint64_t updates = 0;
|
||||||
|
|
||||||
uint64_t updates = 0;
|
while (accumulator >= dt) {
|
||||||
|
accumulator -= dt;
|
||||||
while (accumulator >= dt) {
|
t += dt;
|
||||||
accumulator -= dt;
|
updates++;
|
||||||
t += dt;
|
}
|
||||||
updates++;
|
// std::println("Updates: {}", updates);
|
||||||
}
|
// std::println("frame time: {}", ((double) (frame_time) / (double) glfwGetTimerFrequency()));
|
||||||
// std::println("Updates: {}", updates);
|
// std::println("fps: {}", 1.0 / ((double) (frame_time) / (double) glfwGetTimerFrequency()));
|
||||||
// std::println("frame time: {}", ((double) (frame_time) / (double) glfwGetTimerFrequency()));
|
|
||||||
|
begin_frame();
|
||||||
renderer.begin_frame();
|
|
||||||
|
double f = (t / (double) glfwGetTimerFrequency());
|
||||||
double f = 15.0 * (t / (double) glfwGetTimerFrequency());
|
std::println("{}", f);
|
||||||
|
|
||||||
renderer.submit_quad(
|
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);
|
||||||
100.0 + 10.0 * glm::sin(f),
|
}
|
||||||
100.0 - 10.0 * glm::cos(f),
|
|
||||||
},
|
end_frame(window);
|
||||||
{100.0, 100.0});
|
}
|
||||||
|
|
||||||
renderer.submit_quad({400.0, 400.0}, {20.0, 20.0});
|
void deinit() {
|
||||||
|
graphics_deinit();
|
||||||
// renderer.submit_sprite();
|
glfwDestroyWindow(window);
|
||||||
|
glfwTerminate();
|
||||||
renderer.end_frame();
|
}
|
||||||
|
|
||||||
}
|
int main() {
|
||||||
|
init();
|
||||||
vkDeviceWaitIdle(device);
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
emscripten_set_main_loop_arg(main_loop, nullptr, 0, true);
|
||||||
glfwDestroyWindow(window);
|
#else
|
||||||
glfwTerminate();
|
while (is_running()) {
|
||||||
|
main_loop(nullptr);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
deinit();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
20
misc.cpp
20
misc.cpp
@ -5,15 +5,17 @@
|
|||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include <fstream>
|
#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);
|
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
2
misc.h
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
std::vector<char> loadFile(const char* path);
|
std::string read_entire_file(const std::string &path);
|
||||||
|
|
||||||
|
|
||||||
template<typename T, typename F>
|
template<typename T, typename F>
|
||||||
|
|||||||
14
renderer/graphics.cpp
Normal file
14
renderer/graphics.cpp
Normal 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
48
renderer/graphics.h
Normal 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
|
||||||
16
renderer/graphics_private.h
Normal file
16
renderer/graphics_private.h
Normal 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
|
||||||
@ -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
|
|
||||||
637
renderer/metal/AAPLMathUtilities.cpp
Normal file
637
renderer/metal/AAPLMathUtilities.cpp
Normal 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;
|
||||||
|
}
|
||||||
266
renderer/metal/AAPLMathUtilities.h
Normal file
266
renderer/metal/AAPLMathUtilities.h
Normal 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
206
renderer/metal/metal.cpp
Normal 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
34
renderer/metal/metal.h
Normal 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
379
renderer/metal/renderer.cpp
Normal 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
185
renderer/metal/renderer.h
Normal 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
|
||||||
57
renderer/metal/vertex_data.h
Normal file
57
renderer/metal/vertex_data.h
Normal 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;
|
||||||
|
};
|
||||||
@ -2,8 +2,8 @@
|
|||||||
// Created by Vicente Ferrari Smith on 14.02.26.
|
// Created by Vicente Ferrari Smith on 14.02.26.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef V_SPRITE_H
|
#ifndef SPRITE_H
|
||||||
#define V_SPRITE_H
|
#define SPRITE_H
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include "texture_sheet.h"
|
#include "texture_sheet.h"
|
||||||
@ -17,8 +17,7 @@ struct sprite_t {
|
|||||||
bool window_space;
|
bool window_space;
|
||||||
bool maintain_ar;
|
bool maintain_ar;
|
||||||
|
|
||||||
texture_sheet_id texture_sheet;
|
texture_id texture;
|
||||||
texture_cell_id texture_cell;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //V_SPRITE_H
|
#endif //SPRITE_H
|
||||||
@ -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 "texture.h"
|
||||||
#include <stb_image.h>
|
#include "graphics.h"
|
||||||
#include "renderer.h"
|
|
||||||
|
|
||||||
TextureManager::TextureManager() {
|
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!
|
// Dedup: Don't load the same file twice!
|
||||||
// if (path_to_id.contains(path)) return path_to_id[path];
|
// 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.height = h;
|
||||||
res.channels = STBI_rgb_alpha;
|
res.channels = STBI_rgb_alpha;
|
||||||
res.srgb = true;
|
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);
|
stbi_image_free(data);
|
||||||
|
|
||||||
|
|||||||
@ -2,15 +2,15 @@
|
|||||||
// Created by Vicente Ferrari Smith on 14.02.26.
|
// Created by Vicente Ferrari Smith on 14.02.26.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef V_TEXTURE_H
|
#ifndef TEXTURE_H
|
||||||
#define V_TEXTURE_H
|
#define TEXTURE_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <volk/volk.h>
|
#include <stb_image.h>
|
||||||
#include <vma/vk_mem_alloc.h>
|
|
||||||
|
|
||||||
struct Renderer;
|
struct Renderer;
|
||||||
|
struct PlatformTexture;
|
||||||
|
|
||||||
typedef std::string texture_id;
|
typedef std::string texture_id;
|
||||||
|
|
||||||
@ -26,9 +26,7 @@ struct Texture {
|
|||||||
bool srgb;
|
bool srgb;
|
||||||
bool uploaded;
|
bool uploaded;
|
||||||
|
|
||||||
VkImage image;
|
PlatformTexture *p_texture;
|
||||||
VmaAllocation allocation;
|
|
||||||
VkImageView view;
|
|
||||||
uint32_t descriptor_index;
|
uint32_t descriptor_index;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -36,9 +34,9 @@ struct TextureManager {
|
|||||||
std::unordered_map<texture_id, Texture> textures;
|
std::unordered_map<texture_id, Texture> textures;
|
||||||
|
|
||||||
TextureManager();
|
TextureManager();
|
||||||
Texture load(const std::string& path, Renderer &renderer);
|
Texture load(const std::string& path);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline TextureManager texture_manager;
|
inline TextureManager texture_manager;
|
||||||
|
|
||||||
#endif //V_TEXTURE_H
|
#endif //TEXTURE_H
|
||||||
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
#include "texture.h"
|
#include "texture.h"
|
||||||
|
|
||||||
inline const std::string TEXTURE_SHEETS_PATH = "data/texture_sheets";
|
inline const std::string TEXTURE_SHEETS_PATH = "data/texture_sheets";
|
||||||
@ -26,7 +25,7 @@ struct TextureCell {
|
|||||||
int64_t cell_x;
|
int64_t cell_x;
|
||||||
int64_t cell_y;
|
int64_t cell_y;
|
||||||
|
|
||||||
glm::vec2 st0;
|
glm::vec2 st0 = {0.0, 0.0};
|
||||||
glm::vec2 st1 = {1.0, 1.0};
|
glm::vec2 st1 = {1.0, 1.0};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,11 +3,12 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
|
#include "../graphics.h"
|
||||||
|
|
||||||
#include <print>
|
#include <print>
|
||||||
|
|
||||||
#include "init.h"
|
#include "vulkan.h"
|
||||||
#include "sprite.h"
|
#include "../sprite.h"
|
||||||
#include <vma/vk_mem_alloc.h>
|
#include <vma/vk_mem_alloc.h>
|
||||||
#include <slang/slang.h>
|
#include <slang/slang.h>
|
||||||
|
|
||||||
@ -152,7 +153,7 @@ void Renderer::create_pipeline_layout() {
|
|||||||
vkCreatePipelineLayout(device, &plci, nullptr, &pipelineLayout);
|
vkCreatePipelineLayout(device, &plci, nullptr, &pipelineLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::createFrameResources() {
|
void Renderer::create_frame_resources() {
|
||||||
|
|
||||||
const VkSemaphoreCreateInfo seci{
|
const VkSemaphoreCreateInfo seci{
|
||||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
.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);
|
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, ©Region);
|
|
||||||
|
|
||||||
// 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 {
|
void Renderer::immediate_submit(std::function<void(VkCommandBuffer)>&& func) const {
|
||||||
VkCommandBufferAllocateInfo allocInfo{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
|
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
|
allocInfo.commandPool = frames[currentFrame].commandPool; // Use a pool created with VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
|
||||||
@ -5,9 +5,13 @@
|
|||||||
#ifndef V_RENDERER_H
|
#ifndef V_RENDERER_H
|
||||||
#define V_RENDERER_H
|
#define V_RENDERER_H
|
||||||
|
|
||||||
#include "init.h"
|
#include "vulkan.h"
|
||||||
#include <volk/volk.h>
|
#include <volk/volk.h>
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
#include <GLFW/emscripten_glfw3.h>
|
||||||
|
#else
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
|
#endif
|
||||||
#define GLM_FORCE_RADIANS
|
#define GLM_FORCE_RADIANS
|
||||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||||
#define GLM_ENABLE_EXPERIMENTAL
|
#define GLM_ENABLE_EXPERIMENTAL
|
||||||
@ -15,15 +19,11 @@
|
|||||||
#include <glm/ext/matrix_clip_space.hpp>
|
#include <glm/ext/matrix_clip_space.hpp>
|
||||||
#include "glm/gtx/string_cast.hpp"
|
#include "glm/gtx/string_cast.hpp"
|
||||||
#include <vma/vk_mem_alloc.h>
|
#include <vma/vk_mem_alloc.h>
|
||||||
#include "sprite.h"
|
#include "../sprite.h"
|
||||||
#include "texture.h"
|
#include "../texture.h"
|
||||||
#include <misc.h>
|
#include <misc.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <slang/slang.h>
|
|
||||||
#include <slang/slang-com-ptr.h>
|
|
||||||
|
|
||||||
inline Slang::ComPtr<slang::IGlobalSession> slangGlobalSession;
|
|
||||||
|
|
||||||
enum class PROJECTION_TYPE : uint8_t {
|
enum class PROJECTION_TYPE : uint8_t {
|
||||||
NONE,
|
NONE,
|
||||||
@ -208,16 +208,6 @@ struct Renderer {
|
|||||||
void create_descriptor_pool();
|
void create_descriptor_pool();
|
||||||
void update_bindless_slot(uint32_t slot, VkImageView view, VkSampler sampler) const;
|
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>
|
template <typename T>
|
||||||
VkPipeline create_graphics_pipeline(
|
VkPipeline create_graphics_pipeline(
|
||||||
VkDevice device,
|
VkDevice device,
|
||||||
@ -2,10 +2,147 @@
|
|||||||
// Created by Vicente Ferrari Smith on 12.02.26.
|
// 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 <print>
|
||||||
#include <vector>
|
#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, ©Region);
|
||||||
|
|
||||||
|
// 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(
|
VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
|
||||||
VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||||
VkDebugUtilsMessageTypeFlagsEXT type,
|
VkDebugUtilsMessageTypeFlagsEXT type,
|
||||||
25
renderer/vulkan/vulkan.h
Normal file
25
renderer/vulkan/vulkan.h
Normal 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
|
||||||
280
renderer/webgpu/renderer.cpp
Normal file
280
renderer/webgpu/renderer.cpp
Normal 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
106
renderer/webgpu/renderer.h
Normal 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
|
||||||
79
renderer/webgpu/utils_emscripten.cpp
Normal file
79
renderer/webgpu/utils_emscripten.cpp
Normal 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
|
||||||
40
renderer/webgpu/utils_emscripten.h
Normal file
40
renderer/webgpu/utils_emscripten.h
Normal 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
427
renderer/webgpu/webgpu.cpp
Normal 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
38
renderer/webgpu/webgpu.h
Normal 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
9
shaders/compute.slang
Normal 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
13
shaders/compute.wgsl
Normal 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
BIN
shaders/shader.air
Normal file
Binary file not shown.
52
shaders/shader.metal
Normal file
52
shaders/shader.metal
Normal 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);
|
||||||
|
}
|
||||||
@ -3,8 +3,7 @@ struct VSInput {
|
|||||||
float2 scale;
|
float2 scale;
|
||||||
float2 uv;
|
float2 uv;
|
||||||
float4 color;
|
float4 color;
|
||||||
float alpha;
|
float alpha;
|
||||||
uint32_t textureID;
|
|
||||||
|
|
||||||
uint vertex_index : SV_VertexID;
|
uint vertex_index : SV_VertexID;
|
||||||
};
|
};
|
||||||
@ -14,10 +13,17 @@ struct VSOutput {
|
|||||||
float2 uv;
|
float2 uv;
|
||||||
float4 color;
|
float4 color;
|
||||||
float alpha;
|
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] = {
|
static const float2 square[6] = {
|
||||||
float2(-0.5, -0.5), // Top-left
|
float2(-0.5, -0.5), // Top-left
|
||||||
@ -30,23 +36,23 @@ static const float2 square[6] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
[shader ("vertex")]
|
[shader ("vertex")]
|
||||||
VSOutput main(VSInput input, uniform float4x4 proj) {
|
VSOutput vs_main(VSInput input) {
|
||||||
VSOutput output;
|
VSOutput output;
|
||||||
|
|
||||||
float2 vertex_pos = square[input.vertex_index % 6];
|
float2 vertex_pos = square[input.vertex_index % 6];
|
||||||
float2 final_pos = (vertex_pos * input.scale) + input.pos;
|
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.uv = input.uv;
|
||||||
output.color = input.color;
|
output.color = input.color;
|
||||||
output.alpha = input.alpha;
|
output.alpha = input.alpha;
|
||||||
output.tex_id = input.textureID;
|
//output.tex_id = input.textureID;
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
[shader("fragment")]
|
[shader("fragment")]
|
||||||
float4 main(VSOutput input) {
|
float4 fs_main(VSOutput input) {
|
||||||
|
float4 texColor = colorTexture.Sample(samplerState, input.uv);
|
||||||
return float4(input.color.rgb, input.alpha);
|
return texColor * float4(input.color.rgb, input.alpha);
|
||||||
}
|
}
|
||||||
|
|||||||
63
shaders/shader.wgsl
Normal file
63
shaders/shader.wgsl
Normal 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
BIN
shaders/shaders.metallib
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user