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" />
|
||||
</component>
|
||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||
<component name="WestSettings"><![CDATA[{}]]></component>
|
||||
</project>
|
||||
209
CMakeLists.txt
209
CMakeLists.txt
@ -5,38 +5,170 @@ set(CMAKE_CXX_STANDARD 23)
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
find_package(Vulkan REQUIRED COMPONENTS volk)
|
||||
add_executable(v main.cpp
|
||||
misc.cpp
|
||||
misc.h
|
||||
renderer/graphics.cpp
|
||||
renderer/graphics.h
|
||||
renderer/texture.cpp
|
||||
renderer/texture.h
|
||||
renderer/texture_sheet.cpp
|
||||
renderer/texture_sheet.h
|
||||
renderer/sprite.cpp
|
||||
renderer/sprite.h
|
||||
renderer/graphics_private.h
|
||||
)
|
||||
|
||||
find_library(SLANG_LIB NAMES slang HINTS "$ENV{VULKAN_SDK}/lib")
|
||||
find_path(SLANG_INCLUDE_DIR NAMES slang/slang.h HINTS "$ENV{VULKAN_SDK}/include")
|
||||
find_file(SLANG_DLL NAMES slang.dll HINTS "$ENV{VULKAN_SDK}/bin")
|
||||
target_include_directories(v
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
if(SLANG_LIB AND SLANG_INCLUDE_DIR)
|
||||
add_library(slang_sdk SHARED IMPORTED)
|
||||
set_target_properties(slang_sdk PROPERTIES
|
||||
IMPORTED_IMPLIB "${SLANG_LIB}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${SLANG_INCLUDE_DIR}"
|
||||
target_link_libraries(v PRIVATE glfw glm::glm stb)
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
message(STATUS "Configuring for Emscripten...")
|
||||
|
||||
# 1. Find all .slang files in the shaders directory
|
||||
file(GLOB SLANG_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.slang")
|
||||
|
||||
set(WGSL_OUTPUTS "")
|
||||
|
||||
# 2. Iterate through the found files
|
||||
foreach(SOURCE_PATH ${SLANG_SOURCES})
|
||||
# Get the filename without the path or extension (e.g., "triangle")
|
||||
get_filename_component(FILENAME_WE ${SOURCE_PATH} NAME_WE)
|
||||
|
||||
# Define the output path in the build directory
|
||||
set(OUTPUT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/shaders/${FILENAME_WE}.wgsl")
|
||||
|
||||
# 3. Define the compilation command
|
||||
add_custom_command(
|
||||
OUTPUT ${OUTPUT_PATH}
|
||||
COMMAND slangc ${SOURCE_PATH} -target wgsl -o ${OUTPUT_PATH}
|
||||
DEPENDS ${SOURCE_PATH}
|
||||
COMMENT "Compiling Slang: ${FILENAME_WE}.slang -> ${FILENAME_WE}.wgsl"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
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;
|
||||
# otherwise, on non-Windows systems, SLANG_LIB is the location.
|
||||
if(WIN32 AND SLANG_DLL)
|
||||
set_target_properties(slang_sdk PROPERTIES IMPORTED_LOCATION "${SLANG_DLL}")
|
||||
else()
|
||||
set_target_properties(slang_sdk PROPERTIES IMPORTED_LOCATION "${SLANG_LIB}")
|
||||
endif()
|
||||
target_link_libraries(v PRIVATE
|
||||
"-framework Metal"
|
||||
"-framework MetalKit"
|
||||
"-framework AppKit"
|
||||
"-framework Foundation"
|
||||
"-framework QuartzCore"
|
||||
slang::slang
|
||||
)
|
||||
|
||||
message(STATUS "Slang found via VULKAN_SDK: ${SLANG_LIB}")
|
||||
else()
|
||||
message(FATAL_ERROR "VULKAN_SDK env var is set, but Slang wasn't found inside it!")
|
||||
endif()
|
||||
target_sources(v PRIVATE
|
||||
renderer/metal/metal.cpp
|
||||
renderer/metal/metal.h
|
||||
renderer/metal/renderer.cpp
|
||||
renderer/metal/renderer.h
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
glfw
|
||||
GIT_REPOSITORY https://github.com/glfw/glfw.git
|
||||
GIT_TAG 232164f62b0edbf667cba37c91bab92ffbb020d0
|
||||
)
|
||||
FetchContent_MakeAvailable(glfw)
|
||||
#[[ #shaders
|
||||
|
||||
set(SHADER_DIR ${CMAKE_SOURCE_DIR}/shaders)
|
||||
set(SHADER_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/renderer/metal)
|
||||
|
||||
set(SHADER_OUTPUT_DIR ${CMAKE_SOURCE_DIR}/shaders)
|
||||
|
||||
file(GLOB SHADER_SOURCES
|
||||
${SHADER_DIR}/*.metal)
|
||||
|
||||
set(AIR_FILES)
|
||||
|
||||
foreach(SHADER ${SHADER_SOURCES})
|
||||
get_filename_component(NAME ${SHADER} NAME_WE)
|
||||
set(AIR_FILE ${SHADER_OUTPUT_DIR}/${NAME}.air)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${AIR_FILE}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADER_OUTPUT_DIR}
|
||||
COMMAND xcrun -sdk macosx metal -frecord-sources -gline-tables-only
|
||||
-I ${SHADER_INCLUDE_DIR}
|
||||
-c ${SHADER}
|
||||
-o ${AIR_FILE}
|
||||
DEPENDS ${SHADER}
|
||||
COMMENT "Compiling ${NAME}.metal"
|
||||
)
|
||||
|
||||
list(APPEND AIR_FILES ${AIR_FILE})
|
||||
endforeach()
|
||||
|
||||
set(METALLIB ${SHADER_OUTPUT_DIR}/shaders.metallib)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${METALLIB}
|
||||
COMMAND xcrun -sdk macosx metallib
|
||||
${AIR_FILES}
|
||||
-o ${METALLIB}
|
||||
DEPENDS ${AIR_FILES}
|
||||
COMMENT "Linking shaders.metallib"
|
||||
)
|
||||
|
||||
add_custom_target(shaders ALL
|
||||
DEPENDS ${METALLIB}
|
||||
)
|
||||
|
||||
add_dependencies(v shaders)]]
|
||||
endif ()
|
||||
|
||||
FetchContent_Declare(
|
||||
glm
|
||||
@ -56,27 +188,6 @@ FetchContent_MakeAvailable(stb)
|
||||
add_library(stb INTERFACE)
|
||||
target_include_directories(stb INTERFACE ${stb_SOURCE_DIR})
|
||||
|
||||
add_executable(v main.cpp
|
||||
renderer/init.cpp
|
||||
renderer/init.h
|
||||
renderer/renderer.cpp
|
||||
renderer/renderer.h
|
||||
misc.cpp
|
||||
misc.h
|
||||
renderer/sprite.cpp
|
||||
renderer/sprite.h
|
||||
renderer/texture.cpp
|
||||
renderer/texture.h
|
||||
renderer/texture_sheet.cpp
|
||||
renderer/texture_sheet.h)
|
||||
|
||||
target_include_directories(v
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(v PRIVATE glfw Vulkan::Vulkan glm::glm stb slang_sdk)
|
||||
|
||||
set(SHADER_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/shaders")
|
||||
set(SHADER_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders")
|
||||
file(GLOB_RECURSE SHADER_SOURCES
|
||||
@ -85,7 +196,7 @@ file(GLOB_RECURSE SHADER_SOURCES
|
||||
"${SHADER_SOURCE_DIR}/*.comp"
|
||||
)
|
||||
|
||||
set(SPIRV_BINARY_FILES "")
|
||||
#[[set(SPIRV_BINARY_FILES "")
|
||||
|
||||
foreach(SHADER ${SHADER_SOURCES})
|
||||
|
||||
@ -104,4 +215,4 @@ endforeach()
|
||||
|
||||
add_custom_target(compile_shaders ALL DEPENDS ${SPIRV_BINARY_FILES})
|
||||
|
||||
add_dependencies(v compile_shaders)
|
||||
add_dependencies(v compile_shaders)]]
|
||||
|
||||
151
main.cpp
151
main.cpp
@ -2,16 +2,17 @@
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
#define VOLK_IMPLEMENTATION
|
||||
#include <Volk/volk.h>
|
||||
#define VMA_IMPLEMENTATION
|
||||
#include <vma/vk_mem_alloc.h>
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <GLFW/emscripten_glfw3.h>
|
||||
#include <emscripten.h>
|
||||
#else
|
||||
#include <GLFW/glfw3.h>
|
||||
#endif
|
||||
|
||||
#include "renderer/init.h"
|
||||
#include "renderer/renderer.h"
|
||||
#include "renderer/texture.h"
|
||||
#include "renderer/graphics.h"
|
||||
#include "renderer/texture_sheet.h"
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
@ -24,14 +25,25 @@ int32_t window_height = 480;
|
||||
uint64_t t = 0;
|
||||
uint64_t accumulator = 0;
|
||||
|
||||
uint64_t dt = 0;
|
||||
uint64_t dt = glfwGetTimerFrequency() / 60; // 1/60 s
|
||||
|
||||
int main() {
|
||||
uint64_t current_time = glfwGetTimerValue();
|
||||
|
||||
sprite_t sprite {
|
||||
.origin = {0.5, 0.5},
|
||||
.scale = {512, 512},
|
||||
.rotation = 0.0,
|
||||
.colour = {1.0, 1.0, 1.0, 1.0},
|
||||
.alpha = 1.0,
|
||||
.window_space = false,
|
||||
.maintain_ar = false,
|
||||
.texture = "assets/boy.png"
|
||||
};
|
||||
|
||||
int init() {
|
||||
if (!glfwInit())
|
||||
return -1;
|
||||
|
||||
dt = (uint64_t) (1.0 / 60.0 * glfwGetTimerFrequency());
|
||||
|
||||
std::println("Hello, Sailor!");
|
||||
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||
@ -41,64 +53,67 @@ int main() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
createInstance(window);
|
||||
createSurface(window);
|
||||
createDevice();
|
||||
graphics_init(window);
|
||||
|
||||
createSwapchain(window);
|
||||
|
||||
slang::createGlobalSession(slangGlobalSession.writeRef());
|
||||
|
||||
Renderer renderer(window);
|
||||
|
||||
texture_manager.load("assets/boy.png", renderer);
|
||||
|
||||
uint64_t current_time = glfwGetTimerValue();
|
||||
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
|
||||
uint64_t new_time = glfwGetTimerValue();
|
||||
uint64_t frame_time = new_time - current_time;
|
||||
current_time = new_time;
|
||||
|
||||
accumulator += frame_time;
|
||||
|
||||
glfwPollEvents();
|
||||
|
||||
uint64_t updates = 0;
|
||||
|
||||
while (accumulator >= dt) {
|
||||
accumulator -= dt;
|
||||
t += dt;
|
||||
updates++;
|
||||
}
|
||||
// std::println("Updates: {}", updates);
|
||||
// std::println("frame time: {}", ((double) (frame_time) / (double) glfwGetTimerFrequency()));
|
||||
|
||||
renderer.begin_frame();
|
||||
|
||||
double f = 15.0 * (t / (double) glfwGetTimerFrequency());
|
||||
|
||||
renderer.submit_quad(
|
||||
{
|
||||
100.0 + 10.0 * glm::sin(f),
|
||||
100.0 - 10.0 * glm::cos(f),
|
||||
},
|
||||
{100.0, 100.0});
|
||||
|
||||
renderer.submit_quad({400.0, 400.0}, {20.0, 20.0});
|
||||
|
||||
// renderer.submit_sprite();
|
||||
|
||||
renderer.end_frame();
|
||||
|
||||
}
|
||||
|
||||
vkDeviceWaitIdle(device);
|
||||
|
||||
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
texture_manager.load("assets/boy.png");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool is_running() {
|
||||
return !glfwWindowShouldClose(window);
|
||||
}
|
||||
|
||||
void main_loop(void *data) {
|
||||
uint64_t new_time = glfwGetTimerValue();
|
||||
uint64_t frame_time = new_time - current_time;
|
||||
current_time = new_time;
|
||||
|
||||
accumulator += frame_time;
|
||||
|
||||
glfwPollEvents();
|
||||
|
||||
uint64_t updates = 0;
|
||||
|
||||
while (accumulator >= dt) {
|
||||
accumulator -= dt;
|
||||
t += dt;
|
||||
updates++;
|
||||
}
|
||||
// std::println("Updates: {}", updates);
|
||||
// std::println("frame time: {}", ((double) (frame_time) / (double) glfwGetTimerFrequency()));
|
||||
// std::println("fps: {}", 1.0 / ((double) (frame_time) / (double) glfwGetTimerFrequency()));
|
||||
|
||||
begin_frame();
|
||||
|
||||
double f = (t / (double) glfwGetTimerFrequency());
|
||||
std::println("{}", f);
|
||||
|
||||
for (int x = sprite.scale.x / 2; x < window_width; x += sprite.scale.x) {
|
||||
submit_sprite({x + 30 * cos(f), 200 + 30 * sin(f)}, sprite);
|
||||
}
|
||||
|
||||
end_frame(window);
|
||||
}
|
||||
|
||||
void deinit() {
|
||||
graphics_deinit();
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
emscripten_set_main_loop_arg(main_loop, nullptr, 0, true);
|
||||
#else
|
||||
while (is_running()) {
|
||||
main_loop(nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
deinit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
20
misc.cpp
20
misc.cpp
@ -5,15 +5,17 @@
|
||||
#include "misc.h"
|
||||
#include <fstream>
|
||||
|
||||
std::vector<char> loadFile(const char* path) {
|
||||
std::string read_entire_file(const std::string &path) {
|
||||
std::ifstream f(path, std::ios::binary | std::ios::ate);
|
||||
if (f.good()) {
|
||||
const uint32_t size = f.tellg();
|
||||
std::vector<char> data(size);
|
||||
f.seekg(0);
|
||||
f.read(data.data(), size);
|
||||
return data;
|
||||
}
|
||||
|
||||
return {};
|
||||
if (!f.is_open()) return {};
|
||||
|
||||
const std::streamsize size = f.tellg();
|
||||
std::string buffer;
|
||||
buffer.resize(static_cast<size_t>(size)); // Pre-allocate the string memory
|
||||
|
||||
f.seekg(0);
|
||||
f.read(&buffer[0], size); // Read directly into the string buffer
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
2
misc.h
2
misc.h
@ -7,7 +7,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
std::vector<char> loadFile(const char* path);
|
||||
std::string read_entire_file(const std::string &path);
|
||||
|
||||
|
||||
template<typename T, typename F>
|
||||
|
||||
14
renderer/graphics.cpp
Normal file
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.
|
||||
//
|
||||
|
||||
#ifndef V_SPRITE_H
|
||||
#define V_SPRITE_H
|
||||
#ifndef SPRITE_H
|
||||
#define SPRITE_H
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include "texture_sheet.h"
|
||||
@ -17,8 +17,7 @@ struct sprite_t {
|
||||
bool window_space;
|
||||
bool maintain_ar;
|
||||
|
||||
texture_sheet_id texture_sheet;
|
||||
texture_cell_id texture_cell;
|
||||
texture_id texture;
|
||||
};
|
||||
|
||||
#endif //V_SPRITE_H
|
||||
#endif //SPRITE_H
|
||||
@ -1,16 +1,15 @@
|
||||
//
|
||||
// Created by Vicente Ferrari Smith on 14.02.26.
|
||||
// Created by Vicente Ferrari Smith on 01.03.26.
|
||||
//
|
||||
|
||||
#include "texture.h"
|
||||
#include <stb_image.h>
|
||||
#include "renderer.h"
|
||||
#include "graphics.h"
|
||||
|
||||
TextureManager::TextureManager() {
|
||||
|
||||
}
|
||||
|
||||
Texture TextureManager::load(const std::string& path, Renderer &renderer) {
|
||||
Texture TextureManager::load(const std::string& path) {
|
||||
// Dedup: Don't load the same file twice!
|
||||
// if (path_to_id.contains(path)) return path_to_id[path];
|
||||
|
||||
@ -23,7 +22,7 @@ Texture TextureManager::load(const std::string& path, Renderer &renderer) {
|
||||
res.height = h;
|
||||
res.channels = STBI_rgb_alpha;
|
||||
res.srgb = true;
|
||||
renderer.upload_texture(w, h, data, &res.image, &res.allocation, &res.view, &res.descriptor_index);
|
||||
upload_texture(w, h, data, &res);
|
||||
|
||||
stbi_image_free(data);
|
||||
|
||||
|
||||
@ -2,15 +2,15 @@
|
||||
// Created by Vicente Ferrari Smith on 14.02.26.
|
||||
//
|
||||
|
||||
#ifndef V_TEXTURE_H
|
||||
#define V_TEXTURE_H
|
||||
#ifndef TEXTURE_H
|
||||
#define TEXTURE_H
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <volk/volk.h>
|
||||
#include <vma/vk_mem_alloc.h>
|
||||
#include <stb_image.h>
|
||||
|
||||
struct Renderer;
|
||||
struct PlatformTexture;
|
||||
|
||||
typedef std::string texture_id;
|
||||
|
||||
@ -26,9 +26,7 @@ struct Texture {
|
||||
bool srgb;
|
||||
bool uploaded;
|
||||
|
||||
VkImage image;
|
||||
VmaAllocation allocation;
|
||||
VkImageView view;
|
||||
PlatformTexture *p_texture;
|
||||
uint32_t descriptor_index;
|
||||
};
|
||||
|
||||
@ -36,9 +34,9 @@ struct TextureManager {
|
||||
std::unordered_map<texture_id, Texture> textures;
|
||||
|
||||
TextureManager();
|
||||
Texture load(const std::string& path, Renderer &renderer);
|
||||
Texture load(const std::string& path);
|
||||
};
|
||||
|
||||
inline TextureManager texture_manager;
|
||||
|
||||
#endif //V_TEXTURE_H
|
||||
#endif //TEXTURE_H
|
||||
@ -7,7 +7,6 @@
|
||||
|
||||
#include <string>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "texture.h"
|
||||
|
||||
inline const std::string TEXTURE_SHEETS_PATH = "data/texture_sheets";
|
||||
@ -26,7 +25,7 @@ struct TextureCell {
|
||||
int64_t cell_x;
|
||||
int64_t cell_y;
|
||||
|
||||
glm::vec2 st0;
|
||||
glm::vec2 st0 = {0.0, 0.0};
|
||||
glm::vec2 st1 = {1.0, 1.0};
|
||||
};
|
||||
|
||||
|
||||
@ -3,11 +3,12 @@
|
||||
//
|
||||
|
||||
#include "renderer.h"
|
||||
#include "../graphics.h"
|
||||
|
||||
#include <print>
|
||||
|
||||
#include "init.h"
|
||||
#include "sprite.h"
|
||||
#include "vulkan.h"
|
||||
#include "../sprite.h"
|
||||
#include <vma/vk_mem_alloc.h>
|
||||
#include <slang/slang.h>
|
||||
|
||||
@ -152,7 +153,7 @@ void Renderer::create_pipeline_layout() {
|
||||
vkCreatePipelineLayout(device, &plci, nullptr, &pipelineLayout);
|
||||
}
|
||||
|
||||
void Renderer::createFrameResources() {
|
||||
void Renderer::create_frame_resources() {
|
||||
|
||||
const VkSemaphoreCreateInfo seci{
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
@ -352,91 +353,6 @@ void Renderer::update_bindless_slot(uint32_t slot, VkImageView view, VkSampler s
|
||||
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
|
||||
}
|
||||
|
||||
void Renderer::upload_texture(
|
||||
const int w,
|
||||
const int h,
|
||||
const void* pixels,
|
||||
VkImage *image,
|
||||
VmaAllocation *allocation,
|
||||
VkImageView *view,
|
||||
uint32_t *descriptor_index)
|
||||
{
|
||||
VkDeviceSize imageSize = w * h * 4;
|
||||
|
||||
// --- 1. Create Staging Buffer (CPU Visible) ---
|
||||
VkBuffer stagingBuffer;
|
||||
VmaAllocation stagingAlloc;
|
||||
|
||||
VkBufferCreateInfo stagingBufferInfo = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
|
||||
stagingBufferInfo.size = imageSize;
|
||||
stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
|
||||
VmaAllocationCreateInfo stagingAllocCreateInfo = {
|
||||
.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
|
||||
.usage = VMA_MEMORY_USAGE_AUTO,
|
||||
};
|
||||
|
||||
VmaAllocationInfo stagingResultInfo;
|
||||
vmaCreateBuffer(allocator, &stagingBufferInfo, &stagingAllocCreateInfo, &stagingBuffer, &stagingAlloc, &stagingResultInfo);
|
||||
|
||||
// Copy raw pixels into the mapped memory provided by VMA
|
||||
memcpy(stagingResultInfo.pMappedData, pixels, imageSize);
|
||||
|
||||
// --- 2. Create GPU Image (Device Local / Tiled) ---
|
||||
VkExtent3D imageExtent = { (uint32_t) w, (uint32_t) h, 1 };
|
||||
|
||||
VkImageCreateInfo imageInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
.extent = imageExtent,
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
|
||||
};
|
||||
|
||||
VmaAllocationCreateInfo imageAllocCreateInfo = {
|
||||
.usage = VMA_MEMORY_USAGE_AUTO,
|
||||
.priority = 1.0f,
|
||||
};
|
||||
|
||||
vmaCreateImage(allocator, &imageInfo, &imageAllocCreateInfo, image, allocation, nullptr);
|
||||
|
||||
// --- 3. The Transfer ---
|
||||
immediate_submit([&](VkCommandBuffer cmd) {
|
||||
// Transition image from UNDEFINED to TRANSFER_DST
|
||||
transition_image_layout(cmd, *image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
VkBufferImageCopy copyRegion = {};
|
||||
copyRegion.bufferOffset = 0;
|
||||
copyRegion.bufferRowLength = 0;
|
||||
copyRegion.bufferImageHeight = 0;
|
||||
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
copyRegion.imageSubresource.mipLevel = 0;
|
||||
copyRegion.imageSubresource.baseArrayLayer = 0;
|
||||
copyRegion.imageSubresource.layerCount = 1;
|
||||
copyRegion.imageExtent = imageExtent;
|
||||
|
||||
vkCmdCopyBufferToImage(cmd, stagingBuffer, *image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©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 {
|
||||
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
|
||||
@ -5,9 +5,13 @@
|
||||
#ifndef V_RENDERER_H
|
||||
#define V_RENDERER_H
|
||||
|
||||
#include "init.h"
|
||||
#include "vulkan.h"
|
||||
#include <volk/volk.h>
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <GLFW/emscripten_glfw3.h>
|
||||
#else
|
||||
#include <GLFW/glfw3.h>
|
||||
#endif
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
@ -15,15 +19,11 @@
|
||||
#include <glm/ext/matrix_clip_space.hpp>
|
||||
#include "glm/gtx/string_cast.hpp"
|
||||
#include <vma/vk_mem_alloc.h>
|
||||
#include "sprite.h"
|
||||
#include "texture.h"
|
||||
#include "../sprite.h"
|
||||
#include "../texture.h"
|
||||
#include <misc.h>
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <slang/slang.h>
|
||||
#include <slang/slang-com-ptr.h>
|
||||
|
||||
inline Slang::ComPtr<slang::IGlobalSession> slangGlobalSession;
|
||||
|
||||
enum class PROJECTION_TYPE : uint8_t {
|
||||
NONE,
|
||||
@ -208,16 +208,6 @@ struct Renderer {
|
||||
void create_descriptor_pool();
|
||||
void update_bindless_slot(uint32_t slot, VkImageView view, VkSampler sampler) const;
|
||||
|
||||
// Returns the resource info so the Manager can store it
|
||||
void upload_texture(
|
||||
int w,
|
||||
int h,
|
||||
const void* pixels,
|
||||
VkImage *image,
|
||||
VmaAllocation *allocation,
|
||||
VkImageView *view,
|
||||
uint32_t *descriptor_index);
|
||||
|
||||
template <typename T>
|
||||
VkPipeline create_graphics_pipeline(
|
||||
VkDevice device,
|
||||
@ -2,10 +2,147 @@
|
||||
// Created by Vicente Ferrari Smith on 12.02.26.
|
||||
//
|
||||
|
||||
#include "init.h"
|
||||
#define VOLK_IMPLEMENTATION
|
||||
#include <Volk/volk.h>
|
||||
#define VMA_IMPLEMENTATION
|
||||
#include <vma/vk_mem_alloc.h>
|
||||
|
||||
#include "vulkan.h"
|
||||
#include <print>
|
||||
#include <vector>
|
||||
|
||||
#include "../graphics.h"
|
||||
|
||||
VkInstance instance{};
|
||||
VkPhysicalDevice physicalDevice{};
|
||||
VkDevice device{};
|
||||
VkQueue graphics_queue{};
|
||||
uint32_t queueFamily{};
|
||||
|
||||
VkSurfaceKHR surface{};
|
||||
VkDebugUtilsMessengerEXT debugMessenger{};
|
||||
|
||||
VmaAllocator allocator{};
|
||||
|
||||
constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2;
|
||||
constexpr uint32_t MAX_VERTICES_PER_BATCH = 65536;
|
||||
|
||||
VkSwapchainKHR swapchain;
|
||||
VkExtent2D swapchain_extent;
|
||||
VkSurfaceFormatKHR swapchain_format{
|
||||
VK_FORMAT_B8G8R8A8_UNORM,
|
||||
VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
|
||||
};
|
||||
std::vector<VkSemaphore> renderFinished;
|
||||
|
||||
std::vector<VkImage> images;
|
||||
std::vector<VkImageView> imageViews;
|
||||
std::vector<VkImageLayout> imageLayouts;
|
||||
|
||||
void upload_texture(
|
||||
const int w,
|
||||
const int h,
|
||||
const void* pixels,
|
||||
Texture *texture)
|
||||
{
|
||||
VkDeviceSize imageSize = w * h * 4;
|
||||
|
||||
// --- 1. Create Staging Buffer (CPU Visible) ---
|
||||
VkBuffer stagingBuffer;
|
||||
VmaAllocation stagingAlloc;
|
||||
|
||||
VkBufferCreateInfo stagingBufferInfo = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
|
||||
stagingBufferInfo.size = imageSize;
|
||||
stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
|
||||
VmaAllocationCreateInfo stagingAllocCreateInfo = {
|
||||
.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
|
||||
.usage = VMA_MEMORY_USAGE_AUTO,
|
||||
};
|
||||
|
||||
VmaAllocationInfo stagingResultInfo;
|
||||
vmaCreateBuffer(allocator, &stagingBufferInfo, &stagingAllocCreateInfo, &stagingBuffer, &stagingAlloc, &stagingResultInfo);
|
||||
|
||||
// Copy raw pixels into the mapped memory provided by VMA
|
||||
memcpy(stagingResultInfo.pMappedData, pixels, imageSize);
|
||||
|
||||
// --- 2. Create GPU Image (Device Local / Tiled) ---
|
||||
VkExtent3D imageExtent = { (uint32_t) w, (uint32_t) h, 1 };
|
||||
|
||||
VkImageCreateInfo imageInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
.extent = imageExtent,
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
|
||||
};
|
||||
|
||||
VmaAllocationCreateInfo imageAllocCreateInfo = {
|
||||
.usage = VMA_MEMORY_USAGE_AUTO,
|
||||
.priority = 1.0f,
|
||||
};
|
||||
|
||||
vmaCreateImage(allocator, &imageInfo, &imageAllocCreateInfo, image, allocation, nullptr);
|
||||
|
||||
// --- 3. The Transfer ---
|
||||
immediate_submit([&](VkCommandBuffer cmd) {
|
||||
// Transition image from UNDEFINED to TRANSFER_DST
|
||||
transition_image_layout(cmd, *image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
VkBufferImageCopy copyRegion = {};
|
||||
copyRegion.bufferOffset = 0;
|
||||
copyRegion.bufferRowLength = 0;
|
||||
copyRegion.bufferImageHeight = 0;
|
||||
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
copyRegion.imageSubresource.mipLevel = 0;
|
||||
copyRegion.imageSubresource.baseArrayLayer = 0;
|
||||
copyRegion.imageSubresource.layerCount = 1;
|
||||
copyRegion.imageExtent = imageExtent;
|
||||
|
||||
vkCmdCopyBufferToImage(cmd, stagingBuffer, *image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©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(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
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 uv;
|
||||
float4 color;
|
||||
float alpha;
|
||||
uint32_t textureID;
|
||||
float alpha;
|
||||
|
||||
uint vertex_index : SV_VertexID;
|
||||
};
|
||||
@ -14,10 +13,17 @@ struct VSOutput {
|
||||
float2 uv;
|
||||
float4 color;
|
||||
float alpha;
|
||||
uint32_t tex_id;
|
||||
//uint32_t tex_id;
|
||||
};
|
||||
|
||||
Sampler2D textures[];
|
||||
struct Uniforms {
|
||||
float4x4 proj;
|
||||
};
|
||||
|
||||
ConstantBuffer<Uniforms> uniforms : register(b1);
|
||||
|
||||
Texture2D colorTexture : register(t0);
|
||||
SamplerState samplerState;
|
||||
|
||||
static const float2 square[6] = {
|
||||
float2(-0.5, -0.5), // Top-left
|
||||
@ -30,23 +36,23 @@ static const float2 square[6] = {
|
||||
};
|
||||
|
||||
[shader ("vertex")]
|
||||
VSOutput main(VSInput input, uniform float4x4 proj) {
|
||||
VSOutput vs_main(VSInput input) {
|
||||
VSOutput output;
|
||||
|
||||
float2 vertex_pos = square[input.vertex_index % 6];
|
||||
float2 final_pos = (vertex_pos * input.scale) + input.pos;
|
||||
|
||||
output.pos = mul(proj, float4(final_pos, 0.0, 1.0));
|
||||
output.pos = mul(uniforms.proj, float4(final_pos, 0.0, 1.0));
|
||||
output.uv = input.uv;
|
||||
output.color = input.color;
|
||||
output.alpha = input.alpha;
|
||||
output.tex_id = input.textureID;
|
||||
//output.tex_id = input.textureID;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
[shader("fragment")]
|
||||
float4 main(VSOutput input) {
|
||||
|
||||
return float4(input.color.rgb, input.alpha);
|
||||
float4 fs_main(VSOutput input) {
|
||||
float4 texColor = colorTexture.Sample(samplerState, input.uv);
|
||||
return texColor * float4(input.color.rgb, input.alpha);
|
||||
}
|
||||
|
||||
63
shaders/shader.wgsl
Normal file
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