mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-05-10 22:15:06 +12:00
metal: add all the files
This commit is contained in:
parent
0602467c61
commit
98b5d56021
18 changed files with 3041 additions and 12 deletions
75
include/renderer_mtl/mtl_blit_pipeline_cache.hpp
Normal file
75
include/renderer_mtl/mtl_blit_pipeline_cache.hpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#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
|
86
include/renderer_mtl/mtl_depth_stencil_cache.hpp
Normal file
86
include/renderer_mtl/mtl_depth_stencil_cache.hpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "pica_to_mtl.hpp"
|
||||
|
||||
using namespace PICA;
|
||||
|
||||
namespace Metal {
|
||||
|
||||
struct DepthStencilHash {
|
||||
bool depthStencilWrite;
|
||||
u8 depthFunc;
|
||||
u32 stencilConfig;
|
||||
u16 stencilOpConfig;
|
||||
};
|
||||
|
||||
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
|
174
include/renderer_mtl/mtl_draw_pipeline_cache.hpp
Normal file
174
include/renderer_mtl/mtl_draw_pipeline_cache.hpp
Normal file
|
@ -0,0 +1,174 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "pica_to_mtl.hpp"
|
||||
|
||||
using namespace PICA;
|
||||
|
||||
namespace Metal {
|
||||
|
||||
struct DrawFragmentFunctionHash {
|
||||
bool lightingEnabled; // 1 bit
|
||||
u8 lightingNumLights; // 3 bits
|
||||
u32 lightingConfig1; // 32 bits (TODO: check this)
|
||||
// | ref | func | on |
|
||||
u16 alphaControl; // 12 bits (mask: 11111111 0111 0001)
|
||||
};
|
||||
|
||||
//bool operator==(const DrawFragmentFunctionHash& l, const DrawFragmentFunctionHash& r) {
|
||||
// return ((l.lightingEnabled == r.lightingEnabled) && (l.lightingNumLights == r.lightingNumLights) &&
|
||||
// (l.lightingConfig1 == r.lightingConfig1) && (l.alphaControl == r.alphaControl));
|
||||
//}
|
||||
|
||||
inline bool operator<(const DrawFragmentFunctionHash& l, const DrawFragmentFunctionHash& r) {
|
||||
if (!l.lightingEnabled && r.lightingEnabled) return true;
|
||||
if (l.lightingNumLights < r.lightingNumLights) return true;
|
||||
if (l.lightingConfig1 < r.lightingConfig1) return true;
|
||||
if (l.alphaControl < r.alphaControl) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct DrawPipelineHash { // 56 bits
|
||||
// Formats
|
||||
ColorFmt colorFmt; // 3 bits
|
||||
DepthFmt depthFmt; // 3 bits
|
||||
|
||||
// Blending
|
||||
bool blendEnabled; // 1 bit
|
||||
// | functions | aeq | ceq |
|
||||
u32 blendControl; // 22 bits (mask: 1111111111111111 00000111 00000111)
|
||||
u8 colorWriteMask; // 4 bits
|
||||
|
||||
DrawFragmentFunctionHash fragHash;
|
||||
};
|
||||
|
||||
//bool operator==(const DrawPipelineHash& l, const DrawPipelineHash& r) {
|
||||
// return (((u32)l.colorFmt == (u32)r.colorFmt) && ((u32)l.depthFmt == (u32)r.depthFmt) &&
|
||||
// (l.blendEnabled == r.blendEnabled) && (l.blendControl == r.blendControl) &&
|
||||
// (l.colorWriteMask == r.colorWriteMask) && (l.fragHash == r.fragHash));
|
||||
//}
|
||||
|
||||
inline bool operator<(const DrawPipelineHash& l, const DrawPipelineHash& r) {
|
||||
if ((u32)l.colorFmt < (u32)r.colorFmt) return true;
|
||||
if ((u32)l.depthFmt < (u32)r.depthFmt) return true;
|
||||
if (!l.blendEnabled && r.blendEnabled) return true;
|
||||
if (l.blendControl < r.blendControl) return true;
|
||||
if (l.colorWriteMask < r.colorWriteMask) return true;
|
||||
if (l.fragHash < r.fragHash) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bind the vertex buffer to binding 30 so that it doesn't occupy the lower indices
|
||||
#define VERTEX_BUFFER_BINDING_INDEX 30
|
||||
|
||||
// This pipeline only caches the pipeline with all of its color and depth attachment variations
|
||||
class DrawPipelineCache {
|
||||
public:
|
||||
DrawPipelineCache() = default;
|
||||
|
||||
~DrawPipelineCache() {
|
||||
reset();
|
||||
vertexDescriptor->release();
|
||||
vertexFunction->release();
|
||||
}
|
||||
|
||||
void set(MTL::Device* dev, MTL::Library* lib, MTL::Function* vert, MTL::VertexDescriptor* vertDesc) {
|
||||
device = dev;
|
||||
library = lib;
|
||||
vertexFunction = vert;
|
||||
vertexDescriptor = vertDesc;
|
||||
}
|
||||
|
||||
MTL::RenderPipelineState* get(DrawPipelineHash hash) {
|
||||
//u32 fragmentFunctionHash = ((u32)hash.lightingEnabled << 22) | ((u32)hash.lightingNumLights << 19) | ((u32)hash.lightingConfig1 << 12) | ((((u32)hash.alphaControl & 0b1111111100000000) >> 8) << 4) | ((((u32)hash.alphaControl & 0b01110000) >> 4) << 1) | ((u32)hash.alphaControl & 0b0001);
|
||||
//u64 pipelineHash = ((u64)hash.colorFmt << 53) | ((u64)hash.depthFmt << 50) | ((u64)hash.blendEnabled << 49) | ((u64)hash.colorWriteMask << 45) | ((((u64)hash.blendControl & 0b11111111111111110000000000000000) >> 16) << 29) | ((((u64)hash.blendControl & 0b0000011100000000) >> 8) << 26) | (((u64)hash.blendControl & 0b00000111) << 23) | fragmentFunctionHash;
|
||||
auto& pipeline = pipelineCache[hash];
|
||||
if (!pipeline) {
|
||||
auto& fragmentFunction = fragmentFunctionCache[hash.fragHash];
|
||||
if (!fragmentFunction) {
|
||||
MTL::FunctionConstantValues* constants = MTL::FunctionConstantValues::alloc()->init();
|
||||
constants->setConstantValue(&hash.fragHash.lightingEnabled, MTL::DataTypeBool, NS::UInteger(0));
|
||||
constants->setConstantValue(&hash.fragHash.lightingNumLights, MTL::DataTypeUChar, NS::UInteger(1));
|
||||
constants->setConstantValue(&hash.fragHash.lightingConfig1, MTL::DataTypeUInt, NS::UInteger(2));
|
||||
constants->setConstantValue(&hash.fragHash.alphaControl, MTL::DataTypeUShort, NS::UInteger(3));
|
||||
|
||||
NS::Error* error = nullptr;
|
||||
fragmentFunction = library->newFunction(NS::String::string("fragmentDraw", NS::ASCIIStringEncoding), constants, &error);
|
||||
if (error) {
|
||||
Helpers::panic("Error creating draw fragment function: %s", error->description()->cString(NS::ASCIIStringEncoding));
|
||||
}
|
||||
constants->release();
|
||||
}
|
||||
|
||||
MTL::RenderPipelineDescriptor* desc = MTL::RenderPipelineDescriptor::alloc()->init();
|
||||
desc->setVertexFunction(vertexFunction);
|
||||
desc->setFragmentFunction(fragmentFunction);
|
||||
desc->setVertexDescriptor(vertexDescriptor);
|
||||
|
||||
auto colorAttachment = desc->colorAttachments()->object(0);
|
||||
colorAttachment->setPixelFormat(toMTLPixelFormatColor(hash.colorFmt));
|
||||
MTL::ColorWriteMask writeMask = 0;
|
||||
if (hash.colorWriteMask & 0x1) writeMask |= MTL::ColorWriteMaskRed;
|
||||
if (hash.colorWriteMask & 0x2) writeMask |= MTL::ColorWriteMaskGreen;
|
||||
if (hash.colorWriteMask & 0x4) writeMask |= MTL::ColorWriteMaskBlue;
|
||||
if (hash.colorWriteMask & 0x8) writeMask |= MTL::ColorWriteMaskAlpha;
|
||||
colorAttachment->setWriteMask(writeMask);
|
||||
if (hash.blendEnabled) {
|
||||
const u8 rgbEquation = hash.blendControl & 0x7;
|
||||
const u8 alphaEquation = Helpers::getBits<8, 3>(hash.blendControl);
|
||||
|
||||
// Get blending functions
|
||||
const u8 rgbSourceFunc = Helpers::getBits<16, 4>(hash.blendControl);
|
||||
const u8 rgbDestFunc = Helpers::getBits<20, 4>(hash.blendControl);
|
||||
const u8 alphaSourceFunc = Helpers::getBits<24, 4>(hash.blendControl);
|
||||
const u8 alphaDestFunc = Helpers::getBits<28, 4>(hash.blendControl);
|
||||
|
||||
colorAttachment->setBlendingEnabled(true);
|
||||
colorAttachment->setRgbBlendOperation(toMTLBlendOperation(rgbEquation));
|
||||
colorAttachment->setAlphaBlendOperation(toMTLBlendOperation(alphaEquation));
|
||||
colorAttachment->setSourceRGBBlendFactor(toMTLBlendFactor(rgbSourceFunc));
|
||||
colorAttachment->setDestinationRGBBlendFactor(toMTLBlendFactor(rgbDestFunc));
|
||||
colorAttachment->setSourceAlphaBlendFactor(toMTLBlendFactor(alphaSourceFunc));
|
||||
colorAttachment->setDestinationAlphaBlendFactor(toMTLBlendFactor(alphaDestFunc));
|
||||
}
|
||||
|
||||
desc->setDepthAttachmentPixelFormat(toMTLPixelFormatDepth(hash.depthFmt));
|
||||
|
||||
NS::Error* error = nullptr;
|
||||
desc->setLabel(toNSString("Draw pipeline"));
|
||||
pipeline = device->newRenderPipelineState(desc, &error);
|
||||
if (error) {
|
||||
Helpers::panic("Error creating draw pipeline state: %s", error->description()->cString(NS::ASCIIStringEncoding));
|
||||
}
|
||||
|
||||
desc->release();
|
||||
}
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
for (auto& pair : pipelineCache) {
|
||||
pair.second->release();
|
||||
}
|
||||
pipelineCache.clear();
|
||||
for (auto& pair : fragmentFunctionCache) {
|
||||
pair.second->release();
|
||||
}
|
||||
fragmentFunctionCache.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<DrawPipelineHash, MTL::RenderPipelineState*> pipelineCache;
|
||||
std::map<DrawFragmentFunctionHash, MTL::Function*> fragmentFunctionCache;
|
||||
|
||||
MTL::Device* device;
|
||||
MTL::Library* library;
|
||||
MTL::Function* vertexFunction;
|
||||
MTL::VertexDescriptor* vertexDescriptor;
|
||||
};
|
||||
|
||||
} // namespace Metal
|
92
include/renderer_mtl/mtl_render_target.hpp
Normal file
92
include/renderer_mtl/mtl_render_target.hpp
Normal file
|
@ -0,0 +1,92 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <Metal/Metal.hpp>
|
||||
#include "boost/icl/interval.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "math_util.hpp"
|
||||
#include "opengl.hpp"
|
||||
#include "pica_to_mtl.hpp"
|
||||
#include "objc_helper.hpp"
|
||||
|
||||
template <typename T>
|
||||
using Interval = boost::icl::right_open_interval<T>;
|
||||
|
||||
namespace Metal {
|
||||
|
||||
template <typename Format_t>
|
||||
struct RenderTarget {
|
||||
MTL::Device* device;
|
||||
|
||||
u32 location;
|
||||
Format_t format;
|
||||
OpenGL::uvec2 size;
|
||||
bool valid;
|
||||
|
||||
// Range of VRAM taken up by buffer
|
||||
Interval<u32> range;
|
||||
|
||||
MTL::Texture* texture = nullptr;
|
||||
|
||||
RenderTarget() : valid(false) {}
|
||||
|
||||
RenderTarget(MTL::Device* dev, u32 loc, Format_t format, u32 x, u32 y, bool valid = true)
|
||||
: device(dev), location(loc), format(format), size({x, y}), valid(valid) {
|
||||
u64 endLoc = (u64)loc + sizeInBytes();
|
||||
// Check if start and end are valid here
|
||||
range = Interval<u32>(loc, (u32)endLoc);
|
||||
}
|
||||
|
||||
Math::Rect<u32> getSubRect(u32 inputAddress, u32 width, u32 height) {
|
||||
const u32 startOffset = (inputAddress - location) / sizePerPixel(format);
|
||||
const u32 x0 = (startOffset % (size.x() * 8)) / 8;
|
||||
const u32 y0 = (startOffset / (size.x() * 8)) * 8;
|
||||
return Math::Rect<u32>{x0, size.y() - y0, x0 + width, size.y() - height - y0};
|
||||
}
|
||||
|
||||
// For 2 textures to "match" we only care about their locations, formats, and dimensions to match
|
||||
// For other things, such as filtering mode, etc, we can just switch the attributes of the cached texture
|
||||
bool matches(RenderTarget& other) {
|
||||
return location == other.location && format == other.format &&
|
||||
size.x() == other.size.x() && size.y() == other.size.y();
|
||||
}
|
||||
|
||||
void allocate() {
|
||||
MTL::PixelFormat pixelFormat = MTL::PixelFormatInvalid;
|
||||
if (std::is_same<Format_t, PICA::ColorFmt>::value) {
|
||||
pixelFormat = PICA::toMTLPixelFormatColor((PICA::ColorFmt)format);
|
||||
} else if (std::is_same<Format_t, PICA::DepthFmt>::value) {
|
||||
pixelFormat = PICA::toMTLPixelFormatDepth((PICA::DepthFmt)format);
|
||||
} else {
|
||||
panic("Invalid format type");
|
||||
}
|
||||
|
||||
MTL::TextureDescriptor* descriptor = MTL::TextureDescriptor::alloc()->init();
|
||||
descriptor->setTextureType(MTL::TextureType2D);
|
||||
descriptor->setPixelFormat(pixelFormat);
|
||||
descriptor->setWidth(size.u());
|
||||
descriptor->setHeight(size.v());
|
||||
descriptor->setUsage(MTL::TextureUsageRenderTarget | MTL::TextureUsageShaderRead);
|
||||
descriptor->setStorageMode(MTL::StorageModePrivate);
|
||||
texture = device->newTexture(descriptor);
|
||||
texture->setLabel(toNSString(std::string(std::is_same<Format_t, PICA::ColorFmt>::value ? "Color" : "Depth") + " render target " + std::to_string(size.u()) + "x" + std::to_string(size.v())));
|
||||
descriptor->release();
|
||||
}
|
||||
|
||||
void free() {
|
||||
valid = false;
|
||||
|
||||
if (texture) {
|
||||
texture->release();
|
||||
}
|
||||
}
|
||||
|
||||
u64 sizeInBytes() {
|
||||
return (size_t)size.x() * (size_t)size.y() * PICA::sizePerPixel(format);
|
||||
}
|
||||
};
|
||||
|
||||
typedef RenderTarget<PICA::ColorFmt> ColorRenderTarget;
|
||||
typedef RenderTarget<PICA::DepthFmt> DepthStencilRenderTarget;
|
||||
|
||||
} // namespace Metal
|
77
include/renderer_mtl/mtl_texture.hpp
Normal file
77
include/renderer_mtl/mtl_texture.hpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <Metal/Metal.hpp>
|
||||
#include "PICA/regs.hpp"
|
||||
#include "boost/icl/interval.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "math_util.hpp"
|
||||
#include "opengl.hpp"
|
||||
#include "renderer_mtl/pica_to_mtl.hpp"
|
||||
|
||||
template <typename T>
|
||||
using Interval = boost::icl::right_open_interval<T>;
|
||||
|
||||
namespace Metal {
|
||||
|
||||
struct Texture {
|
||||
MTL::Device* device;
|
||||
|
||||
u32 location;
|
||||
u32 config; // Magnification/minification filter, wrapping configs, etc
|
||||
PICA::TextureFmt format;
|
||||
OpenGL::uvec2 size;
|
||||
bool valid;
|
||||
|
||||
// Range of VRAM taken up by buffer
|
||||
Interval<u32> range;
|
||||
|
||||
PICA::PixelFormatInfo formatInfo;
|
||||
MTL::Texture* texture = nullptr;
|
||||
MTL::SamplerState* sampler = nullptr;
|
||||
|
||||
Texture() : valid(false) {}
|
||||
|
||||
Texture(MTL::Device* dev, u32 loc, PICA::TextureFmt format, u32 x, u32 y, u32 config, bool valid = true)
|
||||
: device(dev), location(loc), format(format), size({x, y}), config(config), valid(valid) {
|
||||
|
||||
u64 endLoc = (u64)loc + sizeInBytes();
|
||||
// Check if start and end are valid here
|
||||
range = Interval<u32>(loc, (u32)endLoc);
|
||||
}
|
||||
|
||||
// For 2 textures to "match" we only care about their locations, formats, and dimensions to match
|
||||
// For other things, such as filtering mode, etc, we can just switch the attributes of the cached texture
|
||||
bool matches(Texture& other) {
|
||||
return location == other.location && format == other.format &&
|
||||
size.x() == other.size.x() && size.y() == other.size.y();
|
||||
}
|
||||
|
||||
void allocate();
|
||||
void setNewConfig(u32 newConfig);
|
||||
void decodeTexture(std::span<const u8> data);
|
||||
void free();
|
||||
u64 sizeInBytes();
|
||||
|
||||
u8 decodeTexelU8(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data);
|
||||
u16 decodeTexelU16(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data);
|
||||
u32 decodeTexelU32(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data);
|
||||
|
||||
// Get the morton interleave offset of a texel based on its U and V values
|
||||
static u32 mortonInterleave(u32 u, u32 v);
|
||||
// Get the byte offset of texel (u, v) in the texture
|
||||
static u32 getSwizzledOffset(u32 u, u32 v, u32 width, u32 bytesPerPixel);
|
||||
static u32 getSwizzledOffset_4bpp(u32 u, u32 v, u32 width);
|
||||
|
||||
// Returns the format of this texture as a string
|
||||
std::string_view formatToString() {
|
||||
return PICA::textureFormatToString(format);
|
||||
}
|
||||
|
||||
// Returns the texel at coordinates (u, v) of an ETC1(A4) texture
|
||||
// TODO: Make hasAlpha a template parameter
|
||||
u32 getTexelETC(bool hasAlpha, u32 u, u32 v, u32 width, std::span<const u8> data);
|
||||
u32 decodeETC(u32 alpha, u32 u, u32 v, u64 colourData);
|
||||
};
|
||||
|
||||
} // namespace Metal
|
80
include/renderer_mtl/mtl_vertex_buffer_cache.hpp
Normal file
80
include/renderer_mtl/mtl_vertex_buffer_cache.hpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
#pragma once
|
||||
|
||||
#include "pica_to_mtl.hpp"
|
||||
|
||||
using namespace PICA;
|
||||
|
||||
namespace Metal {
|
||||
|
||||
struct BufferHandle {
|
||||
MTL::Buffer* buffer;
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
// 64MB buffer for caching vertex data
|
||||
#define CACHE_BUFFER_SIZE 64 * 1024 * 1024
|
||||
|
||||
class VertexBufferCache {
|
||||
public:
|
||||
VertexBufferCache() = default;
|
||||
|
||||
~VertexBufferCache() {
|
||||
endFrame();
|
||||
buffer->release();
|
||||
}
|
||||
|
||||
void set(MTL::Device* dev) {
|
||||
device = dev;
|
||||
create();
|
||||
}
|
||||
|
||||
void endFrame() {
|
||||
ptr = 0;
|
||||
for (auto buffer : additionalAllocations) {
|
||||
buffer->release();
|
||||
}
|
||||
additionalAllocations.clear();
|
||||
}
|
||||
|
||||
BufferHandle get(const void* data, size_t size) {
|
||||
// If the vertex buffer is too large, just create a new one
|
||||
if (ptr + size > CACHE_BUFFER_SIZE) {
|
||||
MTL::Buffer* newBuffer = device->newBuffer(data, size, MTL::ResourceStorageModeShared);
|
||||
newBuffer->setLabel(toNSString("Additional vertex buffer"));
|
||||
additionalAllocations.push_back(newBuffer);
|
||||
Helpers::warn("Vertex buffer doesn't have enough space, creating a new buffer");
|
||||
|
||||
return BufferHandle{newBuffer, 0};
|
||||
}
|
||||
|
||||
// Copy the data into the buffer
|
||||
memcpy((char*)buffer->contents() + ptr, data, size);
|
||||
|
||||
size_t oldPtr = ptr;
|
||||
ptr += size;
|
||||
|
||||
return BufferHandle{buffer, oldPtr};
|
||||
}
|
||||
|
||||
void reset() {
|
||||
endFrame();
|
||||
if (buffer) {
|
||||
buffer->release();
|
||||
create();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
MTL::Buffer* buffer = nullptr;
|
||||
size_t ptr = 0;
|
||||
std::vector<MTL::Buffer*> additionalAllocations;
|
||||
|
||||
MTL::Device* device;
|
||||
|
||||
void create() {
|
||||
buffer = device->newBuffer(CACHE_BUFFER_SIZE, MTL::ResourceStorageModeShared);
|
||||
buffer->setLabel(toNSString("Shared vertex buffer"));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Metal
|
16
include/renderer_mtl/objc_helper.hpp
Normal file
16
include/renderer_mtl/objc_helper.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <Metal/Metal.hpp>
|
||||
|
||||
namespace Metal {
|
||||
|
||||
dispatch_data_t createDispatchData(const void* data, size_t size);
|
||||
|
||||
} // namespace Metal
|
||||
|
||||
// Cast from std::string to NS::String*
|
||||
inline NS::String* toNSString(const std::string& str) {
|
||||
return NS::String::string(str.c_str(), NS::ASCIIStringEncoding);
|
||||
}
|
155
include/renderer_mtl/pica_to_mtl.hpp
Normal file
155
include/renderer_mtl/pica_to_mtl.hpp
Normal file
|
@ -0,0 +1,155 @@
|
|||
#pragma once
|
||||
|
||||
#include <Metal/Metal.hpp>
|
||||
#include "PICA/regs.hpp"
|
||||
|
||||
namespace PICA {
|
||||
|
||||
struct PixelFormatInfo {
|
||||
MTL::PixelFormat pixelFormat;
|
||||
size_t bytesPerTexel;
|
||||
};
|
||||
|
||||
constexpr PixelFormatInfo pixelFormatInfos[14] = {
|
||||
{MTL::PixelFormatRGBA8Unorm, 4}, // RGBA8
|
||||
{MTL::PixelFormatRGBA8Unorm, 4}, // RGB8
|
||||
{MTL::PixelFormatBGR5A1Unorm, 2}, // RGBA5551
|
||||
{MTL::PixelFormatB5G6R5Unorm, 2}, // RGB565
|
||||
{MTL::PixelFormatABGR4Unorm, 2}, // RGBA4
|
||||
{MTL::PixelFormatRGBA8Unorm, 4}, // IA8
|
||||
{MTL::PixelFormatRG8Unorm, 2}, // RG8
|
||||
{MTL::PixelFormatRGBA8Unorm, 4}, // I8
|
||||
{MTL::PixelFormatA8Unorm, 1}, // A8
|
||||
{MTL::PixelFormatABGR4Unorm, 2}, // IA4
|
||||
{MTL::PixelFormatABGR4Unorm, 2}, // I4
|
||||
{MTL::PixelFormatA8Unorm, 1}, // A4
|
||||
{MTL::PixelFormatRGBA8Unorm, 4}, // ETC1
|
||||
{MTL::PixelFormatRGBA8Unorm, 4}, // ETC1A4
|
||||
};
|
||||
|
||||
inline PixelFormatInfo getPixelFormatInfo(TextureFmt format) {
|
||||
return pixelFormatInfos[static_cast<int>(format)];
|
||||
}
|
||||
|
||||
inline MTL::PixelFormat toMTLPixelFormatColor(ColorFmt format) {
|
||||
switch (format) {
|
||||
case ColorFmt::RGBA8: return MTL::PixelFormatRGBA8Unorm;
|
||||
case ColorFmt::RGB8: return MTL::PixelFormatRGBA8Unorm;
|
||||
case ColorFmt::RGBA5551: return MTL::PixelFormatRGBA8Unorm; // TODO: use MTL::PixelFormatBGR5A1Unorm?
|
||||
case ColorFmt::RGB565: return MTL::PixelFormatRGBA8Unorm; // TODO: use MTL::PixelFormatB5G6R5Unorm?
|
||||
case ColorFmt::RGBA4: return MTL::PixelFormatABGR4Unorm;
|
||||
}
|
||||
}
|
||||
|
||||
inline MTL::PixelFormat toMTLPixelFormatDepth(DepthFmt format) {
|
||||
switch (format) {
|
||||
case DepthFmt::Depth16: return MTL::PixelFormatDepth16Unorm;
|
||||
case DepthFmt::Unknown1: return MTL::PixelFormatInvalid;
|
||||
case DepthFmt::Depth24: return MTL::PixelFormatDepth32Float; // Metal does not support 24-bit depth formats
|
||||
// Apple sillicon doesn't support 24-bit depth buffers, so we use 32-bit instead
|
||||
case DepthFmt::Depth24Stencil8: return MTL::PixelFormatDepth32Float_Stencil8;
|
||||
}
|
||||
}
|
||||
|
||||
inline MTL::CompareFunction toMTLCompareFunc(u8 func) {
|
||||
switch (func) {
|
||||
case 0: return MTL::CompareFunctionNever;
|
||||
case 1: return MTL::CompareFunctionAlways;
|
||||
case 2: return MTL::CompareFunctionEqual;
|
||||
case 3: return MTL::CompareFunctionNotEqual;
|
||||
case 4: return MTL::CompareFunctionLess;
|
||||
case 5: return MTL::CompareFunctionLessEqual;
|
||||
case 6: return MTL::CompareFunctionGreater;
|
||||
case 7: return MTL::CompareFunctionGreaterEqual;
|
||||
default: panic("Unknown compare function %u", func);
|
||||
}
|
||||
|
||||
return MTL::CompareFunctionAlways;
|
||||
}
|
||||
|
||||
inline MTL::BlendOperation toMTLBlendOperation(u8 op) {
|
||||
switch (op) {
|
||||
case 0: return MTL::BlendOperationAdd;
|
||||
case 1: return MTL::BlendOperationSubtract;
|
||||
case 2: return MTL::BlendOperationReverseSubtract;
|
||||
case 3: return MTL::BlendOperationMin;
|
||||
case 4: return MTL::BlendOperationMax;
|
||||
case 5: return MTL::BlendOperationAdd; // Unused (same as 0)
|
||||
case 6: return MTL::BlendOperationAdd; // Unused (same as 0)
|
||||
case 7: return MTL::BlendOperationAdd; // Unused (same as 0)
|
||||
default: panic("Unknown blend operation %u", op);
|
||||
}
|
||||
|
||||
return MTL::BlendOperationAdd;
|
||||
}
|
||||
|
||||
inline MTL::BlendFactor toMTLBlendFactor(u8 factor) {
|
||||
switch (factor) {
|
||||
case 0: return MTL::BlendFactorZero;
|
||||
case 1: return MTL::BlendFactorOne;
|
||||
case 2: return MTL::BlendFactorSourceColor;
|
||||
case 3: return MTL::BlendFactorOneMinusSourceColor;
|
||||
case 4: return MTL::BlendFactorDestinationColor;
|
||||
case 5: return MTL::BlendFactorOneMinusDestinationColor;
|
||||
case 6: return MTL::BlendFactorSourceAlpha;
|
||||
case 7: return MTL::BlendFactorOneMinusSourceAlpha;
|
||||
case 8: return MTL::BlendFactorDestinationAlpha;
|
||||
case 9: return MTL::BlendFactorOneMinusDestinationAlpha;
|
||||
case 10: return MTL::BlendFactorBlendColor;
|
||||
case 11: return MTL::BlendFactorOneMinusBlendColor;
|
||||
case 12: return MTL::BlendFactorBlendAlpha;
|
||||
case 13: return MTL::BlendFactorOneMinusBlendAlpha;
|
||||
case 14: return MTL::BlendFactorSourceAlphaSaturated;
|
||||
case 15: return MTL::BlendFactorOne; // Undocumented
|
||||
default: panic("Unknown blend factor %u", factor);
|
||||
}
|
||||
|
||||
return MTL::BlendFactorOne;
|
||||
}
|
||||
|
||||
inline MTL::StencilOperation toMTLStencilOperation(u8 op) {
|
||||
switch (op) {
|
||||
case 0: return MTL::StencilOperationKeep;
|
||||
case 1: return MTL::StencilOperationZero;
|
||||
case 2: return MTL::StencilOperationReplace;
|
||||
case 3: return MTL::StencilOperationIncrementClamp;
|
||||
case 4: return MTL::StencilOperationDecrementClamp;
|
||||
case 5: return MTL::StencilOperationInvert;
|
||||
case 6: return MTL::StencilOperationIncrementWrap;
|
||||
case 7: return MTL::StencilOperationDecrementWrap;
|
||||
default: panic("Unknown stencil operation %u", op);
|
||||
}
|
||||
|
||||
return MTL::StencilOperationKeep;
|
||||
}
|
||||
|
||||
inline MTL::PrimitiveType toMTLPrimitiveType(PrimType primType) {
|
||||
switch (primType) {
|
||||
case PrimType::TriangleList: return MTL::PrimitiveTypeTriangle;
|
||||
case PrimType::TriangleStrip: return MTL::PrimitiveTypeTriangleStrip;
|
||||
case PrimType::TriangleFan:
|
||||
Helpers::warn("Triangle fans are not supported on Metal, using triangles instead");
|
||||
return MTL::PrimitiveTypeTriangle;
|
||||
case PrimType::GeometryPrimitive:
|
||||
//Helpers::warn("Geometry primitives are not yet, using triangles instead");
|
||||
return MTL::PrimitiveTypeTriangle;
|
||||
}
|
||||
}
|
||||
|
||||
inline MTL::SamplerAddressMode toMTLSamplerAddressMode(u8 addrMode) {
|
||||
switch (addrMode) {
|
||||
case 0: return MTL::SamplerAddressModeClampToEdge;
|
||||
case 1: return MTL::SamplerAddressModeClampToBorderColor;
|
||||
case 2: return MTL::SamplerAddressModeRepeat;
|
||||
case 3: return MTL::SamplerAddressModeMirrorRepeat;
|
||||
case 4: return MTL::SamplerAddressModeClampToEdge;
|
||||
case 5: return MTL::SamplerAddressModeClampToBorderColor;
|
||||
case 6: return MTL::SamplerAddressModeRepeat;
|
||||
case 7: return MTL::SamplerAddressModeRepeat;
|
||||
default: panic("Unknown sampler address mode %u", addrMode);
|
||||
}
|
||||
|
||||
return MTL::SamplerAddressModeClampToEdge;
|
||||
}
|
||||
|
||||
} // namespace PICA
|
189
include/renderer_mtl/renderer_mtl.hpp
Normal file
189
include/renderer_mtl/renderer_mtl.hpp
Normal file
|
@ -0,0 +1,189 @@
|
|||
#include <Metal/Metal.hpp>
|
||||
#include <QuartzCore/QuartzCore.hpp>
|
||||
|
||||
#include "renderer.hpp"
|
||||
#include "mtl_texture.hpp"
|
||||
#include "mtl_render_target.hpp"
|
||||
#include "mtl_blit_pipeline_cache.hpp"
|
||||
#include "mtl_draw_pipeline_cache.hpp"
|
||||
#include "mtl_depth_stencil_cache.hpp"
|
||||
#include "mtl_vertex_buffer_cache.hpp"
|
||||
// HACK: use the OpenGL cache
|
||||
#include "../renderer_gl/surface_cache.hpp"
|
||||
|
||||
class GPU;
|
||||
|
||||
struct Color4 {
|
||||
float r, g, b, a;
|
||||
};
|
||||
|
||||
class RendererMTL final : public Renderer {
|
||||
public:
|
||||
RendererMTL(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
|
||||
~RendererMTL() override;
|
||||
|
||||
void reset() override;
|
||||
void display() override;
|
||||
void initGraphicsContext(SDL_Window* window) override;
|
||||
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override;
|
||||
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
|
||||
void screenshot(const std::string& name) override;
|
||||
void deinitGraphicsContext() override;
|
||||
|
||||
#ifdef PANDA3DS_FRONTEND_QT
|
||||
virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override {}
|
||||
#endif
|
||||
|
||||
private:
|
||||
CA::MetalLayer* metalLayer;
|
||||
|
||||
MTL::Device* device;
|
||||
MTL::CommandQueue* commandQueue;
|
||||
|
||||
// Libraries
|
||||
MTL::Library* library;
|
||||
|
||||
// Caches
|
||||
SurfaceCache<Metal::ColorRenderTarget, 16, true> colorRenderTargetCache;
|
||||
SurfaceCache<Metal::DepthStencilRenderTarget, 16, true> depthStencilRenderTargetCache;
|
||||
SurfaceCache<Metal::Texture, 256, true> textureCache;
|
||||
Metal::BlitPipelineCache blitPipelineCache;
|
||||
Metal::DrawPipelineCache drawPipelineCache;
|
||||
Metal::DepthStencilCache depthStencilCache;
|
||||
Metal::VertexBufferCache vertexBufferCache;
|
||||
|
||||
// Objects
|
||||
MTL::SamplerState* nearestSampler;
|
||||
MTL::SamplerState* linearSampler;
|
||||
MTL::Texture* lutTexture;
|
||||
MTL::DepthStencilState* defaultDepthStencilState;
|
||||
|
||||
// Pipelines
|
||||
MTL::RenderPipelineState* displayPipeline;
|
||||
MTL::RenderPipelineState* copyToLutTexturePipeline;
|
||||
|
||||
// Clears
|
||||
std::map<MTL::Texture*, Color4> colorClearOps;
|
||||
std::map<MTL::Texture*, float> depthClearOps;
|
||||
std::map<MTL::Texture*, u8> stencilClearOps;
|
||||
|
||||
// Active state
|
||||
MTL::CommandBuffer* commandBuffer = nullptr;
|
||||
MTL::RenderCommandEncoder* renderCommandEncoder = nullptr;
|
||||
MTL::Texture* lastColorTexture = nullptr;
|
||||
MTL::Texture* lastDepthTexture = nullptr;
|
||||
|
||||
// Debug
|
||||
std::string nextRenderPassName;
|
||||
|
||||
void createCommandBufferIfNeeded() {
|
||||
if (!commandBuffer) {
|
||||
commandBuffer = commandQueue->commandBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void endRenderPass() {
|
||||
if (renderCommandEncoder) {
|
||||
renderCommandEncoder->endEncoding();
|
||||
renderCommandEncoder = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void beginRenderPassIfNeeded(MTL::RenderPassDescriptor* renderPassDescriptor, bool doesClears, MTL::Texture* colorTexture, MTL::Texture* depthTexture = nullptr) {
|
||||
createCommandBufferIfNeeded();
|
||||
|
||||
if (doesClears || !renderCommandEncoder || colorTexture != lastColorTexture || (depthTexture != lastDepthTexture && !(lastDepthTexture && !depthTexture))) {
|
||||
endRenderPass();
|
||||
|
||||
renderCommandEncoder = commandBuffer->renderCommandEncoder(renderPassDescriptor);
|
||||
renderCommandEncoder->setLabel(toNSString(nextRenderPassName));
|
||||
|
||||
lastColorTexture = colorTexture;
|
||||
lastDepthTexture = depthTexture;
|
||||
}
|
||||
|
||||
renderPassDescriptor->release();
|
||||
}
|
||||
|
||||
void commitCommandBuffer() {
|
||||
if (renderCommandEncoder) {
|
||||
renderCommandEncoder->endEncoding();
|
||||
renderCommandEncoder->release();
|
||||
renderCommandEncoder = nullptr;
|
||||
}
|
||||
if (commandBuffer) {
|
||||
commandBuffer->commit();
|
||||
commandBuffer->release();
|
||||
commandBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename AttachmentT, typename ClearDataT, typename GetAttachmentT, typename SetClearDataT>
|
||||
inline void clearAttachment(MTL::RenderPassDescriptor* renderPassDescriptor, MTL::Texture* texture, ClearDataT clearData, GetAttachmentT getAttachment, SetClearDataT setClearData) {
|
||||
bool beginRenderPass = (renderPassDescriptor == nullptr);
|
||||
if (!renderPassDescriptor) {
|
||||
renderPassDescriptor = MTL::RenderPassDescriptor::alloc()->init();
|
||||
}
|
||||
|
||||
AttachmentT* attachment = getAttachment(renderPassDescriptor);
|
||||
attachment->setTexture(texture);
|
||||
setClearData(attachment, clearData);
|
||||
attachment->setLoadAction(MTL::LoadActionClear);
|
||||
attachment->setStoreAction(MTL::StoreActionStore);
|
||||
|
||||
if (beginRenderPass) {
|
||||
if (std::is_same<AttachmentT, MTL::RenderPassColorAttachmentDescriptor>::value)
|
||||
beginRenderPassIfNeeded(renderPassDescriptor, true, texture);
|
||||
else
|
||||
beginRenderPassIfNeeded(renderPassDescriptor, true, nullptr, texture);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename AttachmentT, typename ClearDataT, typename GetAttachmentT, typename SetClearDataT>
|
||||
inline bool clearAttachment(MTL::RenderPassDescriptor* renderPassDescriptor, MTL::Texture* texture, std::map<MTL::Texture*, ClearDataT>& clearOps, GetAttachmentT getAttachment, SetClearDataT setClearData) {
|
||||
auto it = clearOps.find(texture);
|
||||
if (it != clearOps.end()) {
|
||||
clearAttachment<AttachmentT>(renderPassDescriptor, texture, it->second, getAttachment, setClearData);
|
||||
clearOps.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (renderPassDescriptor) {
|
||||
AttachmentT* attachment = getAttachment(renderPassDescriptor);
|
||||
attachment->setTexture(texture);
|
||||
attachment->setLoadAction(MTL::LoadActionLoad);
|
||||
attachment->setStoreAction(MTL::StoreActionStore);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool clearColor(MTL::RenderPassDescriptor* renderPassDescriptor, MTL::Texture* texture) {
|
||||
return clearAttachment<MTL::RenderPassColorAttachmentDescriptor, Color4>(renderPassDescriptor, texture, colorClearOps, [](MTL::RenderPassDescriptor* renderPassDescriptor) { return renderPassDescriptor->colorAttachments()->object(0); }, [](auto attachment, auto& color) {
|
||||
attachment->setClearColor(MTL::ClearColor(color.r, color.g, color.b, color.a));
|
||||
});
|
||||
}
|
||||
|
||||
bool clearDepth(MTL::RenderPassDescriptor* renderPassDescriptor, MTL::Texture* texture) {
|
||||
return clearAttachment<MTL::RenderPassDepthAttachmentDescriptor, float>(renderPassDescriptor, texture, depthClearOps, [](MTL::RenderPassDescriptor* renderPassDescriptor) { return renderPassDescriptor->depthAttachment(); }, [](auto attachment, auto& depth) {
|
||||
attachment->setClearDepth(depth);
|
||||
});
|
||||
}
|
||||
|
||||
bool clearStencil(MTL::RenderPassDescriptor* renderPassDescriptor, MTL::Texture* texture) {
|
||||
return clearAttachment<MTL::RenderPassStencilAttachmentDescriptor, u8>(renderPassDescriptor, texture, stencilClearOps, [](MTL::RenderPassDescriptor* renderPassDescriptor) { return renderPassDescriptor->stencilAttachment(); }, [](auto attachment, auto& stencil) {
|
||||
attachment->setClearStencil(stencil);
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<Metal::ColorRenderTarget> getColorRenderTarget(u32 addr, PICA::ColorFmt format, u32 width, u32 height, bool createIfnotFound = true);
|
||||
Metal::DepthStencilRenderTarget& getDepthRenderTarget();
|
||||
Metal::Texture& getTexture(Metal::Texture& tex);
|
||||
void setupTextureEnvState(MTL::RenderCommandEncoder* encoder);
|
||||
void bindTexturesToSlots(MTL::RenderCommandEncoder* encoder);
|
||||
void updateLightingLUT(MTL::RenderCommandEncoder* encoder);
|
||||
void updateFogLUT(MTL::RenderCommandEncoder* encoder);
|
||||
void textureCopyImpl(Metal::ColorRenderTarget& srcFramebuffer, Metal::ColorRenderTarget& destFramebuffer, const Math::Rect<u32>& srcRect, const Math::Rect<u32>& destRect);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue