Skip to content

Commit

Permalink
Merge pull request #578 from SamoZ256/metal2
Browse files Browse the repository at this point in the history
Metal backend
  • Loading branch information
wheremyfoodat authored Nov 9, 2024
2 parents a1601b5 + e479237 commit b214782
Show file tree
Hide file tree
Showing 31 changed files with 3,225 additions and 15 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,9 @@ fb.bat
*.elf
*.smdh

# Compiled Metal shader files
*.ir
*.metallib

config.toml
CMakeSettings.json
89 changes: 82 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ endif()

if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format-nonliteral -Wno-format-security -Wno-invalid-offsetof")
endif()
endif()

if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-interference-size")
endif()
endif()

if(ANDROID)
set(DEFAULT_OPENGL_PROFILE OpenGLES)
Expand All @@ -49,6 +49,7 @@ option(DISABLE_PANIC_DEV "Make a build with fewer and less intrusive asserts" ON
option(GPU_DEBUG_INFO "Enable additional GPU debugging info" OFF)
option(ENABLE_OPENGL "Enable OpenGL rendering backend" ON)
option(ENABLE_VULKAN "Enable Vulkan rendering backend" ON)
option(ENABLE_METAL "Enable Metal rendering backend (if available)" ON)
option(ENABLE_LTO "Enable link-time optimization" OFF)
option(ENABLE_TESTS "Compile unit-tests" OFF)
option(ENABLE_USER_BUILD "Make a user-facing build. These builds have various assertions disabled, LTO, and more" OFF)
Expand Down Expand Up @@ -82,7 +83,7 @@ endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND ENABLE_USER_BUILD)
# Disable stack buffer overflow checks in user builds
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GS-")
endif()
endif()

# Generate versioning files
find_package(Git)
Expand Down Expand Up @@ -311,7 +312,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
include/services/mic.hpp include/services/cecd.hpp include/services/ac.hpp
include/services/am.hpp include/services/boss.hpp include/services/frd.hpp include/services/nim.hpp
include/fs/archive_ext_save_data.hpp include/fs/archive_ncch.hpp include/services/mcu/mcu_hwc.hpp
include/colour.hpp include/services/y2r.hpp include/services/cam.hpp include/services/ssl.hpp
include/colour.hpp include/services/y2r.hpp include/services/cam.hpp include/services/ssl.hpp
include/services/ldr_ro.hpp include/ipc.hpp include/services/act.hpp include/services/nfc.hpp
include/system_models.hpp include/services/dlp_srvr.hpp include/PICA/dynapica/pica_recs.hpp
include/PICA/dynapica/x64_regs.hpp include/PICA/dynapica/vertex_loader_rec.hpp include/PICA/dynapica/shader_rec.hpp
Expand All @@ -322,7 +323,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
include/config.hpp include/services/ir_user.hpp include/http_server.hpp include/cheats.hpp
include/action_replay.hpp include/renderer_sw/renderer_sw.hpp include/compiler_builtins.hpp
include/fs/romfs.hpp include/fs/ivfc.hpp include/discord_rpc.hpp include/services/http.hpp include/result/result_cfg.hpp
include/applets/applet.hpp include/applets/mii_selector.hpp include/math_util.hpp include/services/soc.hpp
include/applets/applet.hpp include/applets/mii_selector.hpp include/math_util.hpp include/services/soc.hpp
include/services/news_u.hpp include/applets/software_keyboard.hpp include/applets/applet_manager.hpp include/fs/archive_user_save_data.hpp
include/services/amiibo_device.hpp include/services/nfc_types.hpp include/swap.hpp include/services/csnd.hpp include/services/nwm_uds.hpp
include/fs/archive_system_save_data.hpp include/lua_manager.hpp include/memory_mapped_file.hpp include/hydra_icon.hpp
Expand Down Expand Up @@ -491,8 +492,82 @@ if(ENABLE_VULKAN)
target_link_libraries(AlberCore PRIVATE Vulkan::Vulkan resources_renderer_vk)
endif()

if(ENABLE_METAL AND APPLE)
set(RENDERER_MTL_INCLUDE_FILES include/renderer_mtl/renderer_mtl.hpp
include/renderer_mtl/mtl_depth_stencil_cache.hpp
include/renderer_mtl/mtl_blit_pipeline_cache.hpp
include/renderer_mtl/mtl_draw_pipeline_cache.hpp
include/renderer_mtl/mtl_render_target.hpp
include/renderer_mtl/mtl_texture.hpp
include/renderer_mtl/mtl_vertex_buffer_cache.hpp
include/renderer_mtl/mtl_lut_texture.hpp
include/renderer_mtl/mtl_command_encoder.hpp
include/renderer_mtl/mtl_common.hpp
include/renderer_mtl/pica_to_mtl.hpp
include/renderer_mtl/objc_helper.hpp
)

set(RENDERER_MTL_SOURCE_FILES src/core/renderer_mtl/metal_cpp_impl.cpp
src/core/renderer_mtl/renderer_mtl.cpp
src/core/renderer_mtl/mtl_texture.cpp
src/core/renderer_mtl/mtl_etc1.cpp
src/core/renderer_mtl/mtl_lut_texture.cpp
src/core/renderer_mtl/objc_helper.mm
src/host_shaders/metal_shaders.metal
src/host_shaders/metal_blit.metal
#src/host_shaders/metal_copy_to_lut_texture.metal
)

set(HEADER_FILES ${HEADER_FILES} ${RENDERER_MTL_INCLUDE_FILES})
source_group("Source Files\\Core\\Metal Renderer" FILES ${RENDERER_MTL_SOURCE_FILES})

set(RENDERER_MTL_HOST_SHADERS_SOURCES)
function (add_metal_shader SHADER)
set(SHADER_SOURCE "${CMAKE_SOURCE_DIR}/src/host_shaders/${SHADER}.metal")
set(SHADER_IR "${CMAKE_SOURCE_DIR}/src/host_shaders/${SHADER}.ir")
set(SHADER_METALLIB "${CMAKE_SOURCE_DIR}/src/host_shaders/${SHADER}.metallib")
# TODO: only include sources in debug builds
add_custom_command(
OUTPUT ${SHADER_IR}
COMMAND xcrun -sdk macosx metal -gline-tables-only -frecord-sources -o ${SHADER_IR} -c ${SHADER_SOURCE}
DEPENDS ${SHADER_SOURCE}
VERBATIM)
add_custom_command(
OUTPUT ${SHADER_METALLIB}
COMMAND xcrun -sdk macosx metallib -o ${SHADER_METALLIB} ${SHADER_IR}
DEPENDS ${SHADER_IR}
VERBATIM)
set(RENDERER_MTL_HOST_SHADERS_SOURCES ${RENDERER_MTL_HOST_SHADERS_SOURCES} ${SHADER_METALLIB})
endfunction()

add_metal_shader(metal_shaders)
add_metal_shader(metal_blit)
#add_metal_shader(metal_copy_to_lut_texture)

add_custom_target(
compile_msl_shaders
DEPENDS ${RENDERER_MTL_HOST_SHADERS_SOURCES}
)

cmrc_add_resource_library(
resources_renderer_mtl
NAMESPACE RendererMTL
WHENCE "src/host_shaders/"
"src/host_shaders/metal_shaders.metallib"
"src/host_shaders/metal_blit.metallib"
#"src/host_shaders/metal_copy_to_lut_texture.metallib"
)
add_dependencies(resources_renderer_mtl compile_msl_shaders)

target_sources(AlberCore PRIVATE ${RENDERER_MTL_SOURCE_FILES})
target_compile_definitions(AlberCore PUBLIC "PANDA3DS_ENABLE_METAL=1")
target_include_directories(AlberCore PRIVATE third_party/metal-cpp)
# TODO: check if all of them are needed
target_link_libraries(AlberCore PRIVATE "-framework Metal" "-framework Foundation" "-framework QuartzCore" resources_renderer_mtl)
endif()

source_group("Header Files\\Core" FILES ${HEADER_FILES})
set(ALL_SOURCES ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES}
set(ALL_SOURCES ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES}
${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES} ${APPLET_SOURCE_FILES} ${RENDERER_SW_SOURCE_FILES} ${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES}
${AUDIO_SOURCE_FILES} ${HEADER_FILES} ${FRONTEND_HEADER_FILES})
target_sources(AlberCore PRIVATE ${ALL_SOURCES})
Expand Down Expand Up @@ -537,7 +612,7 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE)
if(NOT ENABLE_OPENGL)
message(FATAL_ERROR "Qt frontend requires OpenGL")
endif()

option(GENERATE_QT_TRANSLATION "Generate Qt translation file" OFF)
set(QT_LANGUAGES docs/translations)

Expand Down
1 change: 1 addition & 0 deletions include/panda_qt/main_window.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class MainWindow : public QMainWindow {
// Tracks whether we are using an OpenGL-backed renderer or a Vulkan-backed renderer
bool usingGL = false;
bool usingVk = false;
bool usingMtl = false;

// Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard
// This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state
Expand Down
3 changes: 2 additions & 1 deletion include/renderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ enum class RendererType : s8 {
Null = 0,
OpenGL = 1,
Vulkan = 2,
Software = 3,
Metal = 3,
Software = 4,
};

struct EmulatorConfig;
Expand Down
2 changes: 0 additions & 2 deletions include/renderer_gl/surface_cache.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ template <typename SurfaceType, size_t capacity, bool evictOnOverflow = false>
class SurfaceCache {
// Vanilla std::optional can't hold actual references
using OptionalRef = std::optional<std::reference_wrapper<SurfaceType>>;
static_assert(std::is_same<SurfaceType, ColourBuffer>() || std::is_same<SurfaceType, DepthBuffer>() ||
std::is_same<SurfaceType, Texture>(), "Invalid surface type");

size_t size;
size_t evictionIndex;
Expand Down
74 changes: 74 additions & 0 deletions include/renderer_mtl/mtl_blit_pipeline_cache.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#pragma once

#include <map>

#include "objc_helper.hpp"
#include "pica_to_mtl.hpp"

using namespace PICA;

namespace Metal {
struct BlitPipelineHash {
// Formats
ColorFmt colorFmt;
DepthFmt depthFmt;
};

// This pipeline only caches the pipeline with all of its color and depth attachment variations
class BlitPipelineCache {
public:
BlitPipelineCache() = default;

~BlitPipelineCache() {
reset();
vertexFunction->release();
fragmentFunction->release();
}

void set(MTL::Device* dev, MTL::Function* vert, MTL::Function* frag) {
device = dev;
vertexFunction = vert;
fragmentFunction = frag;
}

MTL::RenderPipelineState* get(BlitPipelineHash hash) {
u8 intHash = ((u8)hash.colorFmt << 3) | (u8)hash.depthFmt;
auto& pipeline = pipelineCache[intHash];
if (!pipeline) {
MTL::RenderPipelineDescriptor* desc = MTL::RenderPipelineDescriptor::alloc()->init();
desc->setVertexFunction(vertexFunction);
desc->setFragmentFunction(fragmentFunction);

auto colorAttachment = desc->colorAttachments()->object(0);
colorAttachment->setPixelFormat(toMTLPixelFormatColor(hash.colorFmt));

desc->setDepthAttachmentPixelFormat(toMTLPixelFormatDepth(hash.depthFmt));

NS::Error* error = nullptr;
desc->setLabel(toNSString("Blit pipeline"));
pipeline = device->newRenderPipelineState(desc, &error);
if (error) {
Helpers::panic("Error creating blit pipeline state: %s", error->description()->cString(NS::ASCIIStringEncoding));
}

desc->release();
}

return pipeline;
}

void reset() {
for (auto& pair : pipelineCache) {
pair.second->release();
}
pipelineCache.clear();
}

private:
std::map<u8, MTL::RenderPipelineState*> pipelineCache;

MTL::Device* device;
MTL::Function* vertexFunction;
MTL::Function* fragmentFunction;
};
} // namespace Metal
56 changes: 56 additions & 0 deletions include/renderer_mtl/mtl_command_encoder.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#pragma once

#include <Metal/Metal.hpp>

namespace Metal {
struct RenderState {
MTL::RenderPipelineState* renderPipelineState = nullptr;
MTL::DepthStencilState* depthStencilState = nullptr;
MTL::Texture* textures[3] = {nullptr};
MTL::SamplerState* samplerStates[3] = {nullptr};
};

class CommandEncoder {
public:
void newRenderCommandEncoder(MTL::RenderCommandEncoder* rce) {
renderCommandEncoder = rce;

// Reset the render state
renderState = RenderState{};
}

// Resource binding
void setRenderPipelineState(MTL::RenderPipelineState* renderPipelineState) {
if (renderPipelineState != renderState.renderPipelineState) {
renderCommandEncoder->setRenderPipelineState(renderPipelineState);
renderState.renderPipelineState = renderPipelineState;
}
}

void setDepthStencilState(MTL::DepthStencilState* depthStencilState) {
if (depthStencilState != renderState.depthStencilState) {
renderCommandEncoder->setDepthStencilState(depthStencilState);
renderState.depthStencilState = depthStencilState;
}
}

void setFragmentTexture(MTL::Texture* texture, u32 index) {
if (texture != renderState.textures[index]) {
renderCommandEncoder->setFragmentTexture(texture, index);
renderState.textures[index] = texture;
}
}

void setFragmentSamplerState(MTL::SamplerState* samplerState, u32 index) {
if (samplerState != renderState.samplerStates[index]) {
renderCommandEncoder->setFragmentSamplerState(samplerState, index);
renderState.samplerStates[index] = samplerState;
}
}

private:
MTL::RenderCommandEncoder* renderCommandEncoder = nullptr;

RenderState renderState;
};
} // namespace Metal
6 changes: 6 additions & 0 deletions include/renderer_mtl/mtl_common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#pragma once

#include <Metal/Metal.hpp>

#define GET_HELPER_TEXTURE_BINDING(binding) (30 - binding)
#define GET_HELPER_SAMPLER_STATE_BINDING(binding) (15 - binding)
80 changes: 80 additions & 0 deletions include/renderer_mtl/mtl_depth_stencil_cache.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#pragma once

#include <map>

#include "pica_to_mtl.hpp"

using namespace PICA;

namespace Metal {
struct DepthStencilHash {
u32 stencilConfig;
u16 stencilOpConfig;
bool depthStencilWrite;
u8 depthFunc;
};

class DepthStencilCache {
public:
DepthStencilCache() = default;

~DepthStencilCache() { reset(); }

void set(MTL::Device* dev) { device = dev; }

MTL::DepthStencilState* get(DepthStencilHash hash) {
u64 intHash =
((u64)hash.depthStencilWrite << 56) | ((u64)hash.depthFunc << 48) | ((u64)hash.stencilConfig << 16) | (u64)hash.stencilOpConfig;
auto& depthStencilState = depthStencilCache[intHash];
if (!depthStencilState) {
MTL::DepthStencilDescriptor* desc = MTL::DepthStencilDescriptor::alloc()->init();
desc->setDepthWriteEnabled(hash.depthStencilWrite);
desc->setDepthCompareFunction(toMTLCompareFunc(hash.depthFunc));

const bool stencilEnable = Helpers::getBit<0>(hash.stencilConfig);
MTL::StencilDescriptor* stencilDesc = nullptr;
if (stencilEnable) {
const u8 stencilFunc = Helpers::getBits<4, 3>(hash.stencilConfig);
const u8 stencilRefMask = Helpers::getBits<24, 8>(hash.stencilConfig);

const u32 stencilBufferMask = hash.depthStencilWrite ? Helpers::getBits<8, 8>(hash.stencilConfig) : 0;

const u8 stencilFailOp = Helpers::getBits<0, 3>(hash.stencilOpConfig);
const u8 depthFailOp = Helpers::getBits<4, 3>(hash.stencilOpConfig);
const u8 passOp = Helpers::getBits<8, 3>(hash.stencilOpConfig);

stencilDesc = MTL::StencilDescriptor::alloc()->init();
stencilDesc->setStencilFailureOperation(toMTLStencilOperation(stencilFailOp));
stencilDesc->setDepthFailureOperation(toMTLStencilOperation(depthFailOp));
stencilDesc->setDepthStencilPassOperation(toMTLStencilOperation(passOp));
stencilDesc->setStencilCompareFunction(toMTLCompareFunc(stencilFunc));
stencilDesc->setReadMask(stencilRefMask);
stencilDesc->setWriteMask(stencilBufferMask);

desc->setFrontFaceStencil(stencilDesc);
desc->setBackFaceStencil(stencilDesc);
}

depthStencilState = device->newDepthStencilState(desc);

desc->release();
if (stencilDesc) {
stencilDesc->release();
}
}

return depthStencilState;
}

void reset() {
for (auto& pair : depthStencilCache) {
pair.second->release();
}
depthStencilCache.clear();
}

private:
std::map<u64, MTL::DepthStencilState*> depthStencilCache;
MTL::Device* device;
};
} // namespace Metal
Loading

0 comments on commit b214782

Please sign in to comment.