123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573 |
- // Copyright 2022 Dolphin Emulator Project
- // SPDX-License-Identifier: GPL-2.0-or-later
- #include "VideoBackends/Metal/MTLObjectCache.h"
- #include <map>
- #include <mutex>
- #include <optional>
- #include "Common/Assert.h"
- #include "Common/MsgHandler.h"
- #include "VideoBackends/Metal/MTLPipeline.h"
- #include "VideoBackends/Metal/MTLUtil.h"
- #include "VideoBackends/Metal/MTLVertexFormat.h"
- #include "VideoCommon/AbstractPipeline.h"
- #include "VideoCommon/NativeVertexFormat.h"
- #include "VideoCommon/VertexShaderGen.h"
- #include "VideoCommon/VideoBackendBase.h"
- #include "VideoCommon/VideoConfig.h"
- MRCOwned<id<MTLDevice>> Metal::g_device;
- MRCOwned<id<MTLCommandQueue>> Metal::g_queue;
- std::unique_ptr<Metal::ObjectCache> Metal::g_object_cache;
- static void SetupDepthStencil(
- MRCOwned<id<MTLDepthStencilState>> (&dss)[Metal::DepthStencilSelector::N_VALUES]);
- Metal::ObjectCache::ObjectCache()
- {
- m_internal = std::make_unique<Internal>();
- SetupDepthStencil(m_dss);
- }
- Metal::ObjectCache::~ObjectCache()
- {
- }
- void Metal::ObjectCache::Initialize(MRCOwned<id<MTLDevice>> device)
- {
- g_device = std::move(device);
- g_queue = MRCTransfer([g_device newCommandQueue]);
- g_object_cache = std::unique_ptr<ObjectCache>(new ObjectCache);
- }
- void Metal::ObjectCache::Shutdown()
- {
- g_object_cache.reset();
- g_queue = nullptr;
- g_device = nullptr;
- }
- // MARK: Depth Stencil State
- // clang-format off
- static MTLCompareFunction Convert(CompareMode mode)
- {
- const bool invert_depth = !g_backend_info.bSupportsReversedDepthRange;
- switch (mode)
- {
- case CompareMode::Never: return MTLCompareFunctionNever;
- case CompareMode::Less: return invert_depth ? MTLCompareFunctionGreater
- : MTLCompareFunctionLess;
- case CompareMode::Equal: return MTLCompareFunctionEqual;
- case CompareMode::LEqual: return invert_depth ? MTLCompareFunctionGreaterEqual
- : MTLCompareFunctionLessEqual;
- case CompareMode::Greater: return invert_depth ? MTLCompareFunctionLess
- : MTLCompareFunctionGreater;
- case CompareMode::NEqual: return MTLCompareFunctionNotEqual;
- case CompareMode::GEqual: return invert_depth ? MTLCompareFunctionLessEqual
- : MTLCompareFunctionGreaterEqual;
- case CompareMode::Always: return MTLCompareFunctionAlways;
- }
- }
- static const char* to_string(MTLCompareFunction compare)
- {
- switch (compare)
- {
- case MTLCompareFunctionNever: return "Never";
- case MTLCompareFunctionGreater: return "Greater";
- case MTLCompareFunctionEqual: return "Equal";
- case MTLCompareFunctionGreaterEqual: return "GEqual";
- case MTLCompareFunctionLess: return "Less";
- case MTLCompareFunctionNotEqual: return "NEqual";
- case MTLCompareFunctionLessEqual: return "LEqual";
- case MTLCompareFunctionAlways: return "Always";
- }
- }
- // clang-format on
- static void
- SetupDepthStencil(MRCOwned<id<MTLDepthStencilState>> (&dss)[Metal::DepthStencilSelector::N_VALUES])
- {
- auto desc = MRCTransfer([MTLDepthStencilDescriptor new]);
- Metal::DepthStencilSelector sel;
- for (size_t i = 0; i < std::size(dss); ++i)
- {
- sel.value = i;
- MTLCompareFunction mcompare = Convert(sel.CompareMode());
- [desc setDepthWriteEnabled:sel.UpdateEnable()];
- [desc setDepthCompareFunction:mcompare];
- [desc setLabel:[NSString stringWithFormat:@"DSS %s%s", to_string(mcompare),
- sel.UpdateEnable() ? "+Write" : ""]];
- dss[i] = MRCTransfer([Metal::g_device newDepthStencilStateWithDescriptor:desc]);
- }
- }
- // MARK: Samplers
- // clang-format off
- static MTLSamplerMinMagFilter ConvertMinMag(FilterMode filter)
- {
- switch (filter)
- {
- case FilterMode::Linear: return MTLSamplerMinMagFilterLinear;
- case FilterMode::Near: return MTLSamplerMinMagFilterNearest;
- }
- }
- static MTLSamplerMipFilter ConvertMip(FilterMode filter)
- {
- switch (filter)
- {
- case FilterMode::Linear: return MTLSamplerMipFilterLinear;
- case FilterMode::Near: return MTLSamplerMipFilterNearest;
- }
- }
- static MTLSamplerAddressMode Convert(WrapMode wrap)
- {
- switch (wrap)
- {
- case WrapMode::Clamp: return MTLSamplerAddressModeClampToEdge;
- case WrapMode::Mirror: return MTLSamplerAddressModeMirrorRepeat;
- case WrapMode::Repeat: return MTLSamplerAddressModeRepeat;
- }
- }
- static const char* to_string(FilterMode filter)
- {
- switch (filter)
- {
- case FilterMode::Linear: return "Ln";
- case FilterMode::Near: return "Pt";
- }
- }
- static const char* to_string(WrapMode wrap)
- {
- switch (wrap)
- {
- case WrapMode::Clamp: return "C";
- case WrapMode::Mirror: return "M";
- case WrapMode::Repeat: return "R";
- }
- }
- // clang-format on
- MRCOwned<id<MTLSamplerState>> Metal::ObjectCache::CreateSampler(SamplerSelector sel)
- {
- @autoreleasepool
- {
- auto desc = MRCTransfer([MTLSamplerDescriptor new]);
- [desc setMinFilter:ConvertMinMag(sel.MinFilter())];
- [desc setMagFilter:ConvertMinMag(sel.MagFilter())];
- [desc setMipFilter:ConvertMip(sel.MipFilter())];
- [desc setSAddressMode:Convert(sel.WrapU())];
- [desc setTAddressMode:Convert(sel.WrapV())];
- [desc setMaxAnisotropy:1 << sel.AnisotropicFiltering()];
- [desc setLabel:MRCTransfer([[NSString alloc]
- initWithFormat:@"%s%s%s %s%s%d", to_string(sel.MinFilter()),
- to_string(sel.MagFilter()), to_string(sel.MipFilter()),
- to_string(sel.WrapU()), to_string(sel.WrapV()),
- 1 << sel.AnisotropicFiltering()])];
- return MRCTransfer([Metal::g_device newSamplerStateWithDescriptor:desc]);
- }
- }
- void Metal::ObjectCache::ReloadSamplers()
- {
- for (auto& sampler : m_samplers)
- sampler = nullptr;
- }
- // MARK: Pipelines
- static MTLPrimitiveTopologyClass GetClass(PrimitiveType prim)
- {
- switch (prim)
- {
- case PrimitiveType::Points:
- return MTLPrimitiveTopologyClassPoint;
- case PrimitiveType::Lines:
- return MTLPrimitiveTopologyClassLine;
- case PrimitiveType::Triangles:
- case PrimitiveType::TriangleStrip:
- return MTLPrimitiveTopologyClassTriangle;
- }
- }
- static MTLPrimitiveType Convert(PrimitiveType prim)
- {
- // clang-format off
- switch (prim)
- {
- case PrimitiveType::Points: return MTLPrimitiveTypePoint;
- case PrimitiveType::Lines: return MTLPrimitiveTypeLine;
- case PrimitiveType::Triangles: return MTLPrimitiveTypeTriangle;
- case PrimitiveType::TriangleStrip: return MTLPrimitiveTypeTriangleStrip;
- }
- // clang-format on
- }
- static MTLCullMode Convert(CullMode cull)
- {
- switch (cull)
- {
- case CullMode::None:
- case CullMode::All: // Handled by VertexLoaderManager::RunVertices
- return MTLCullModeNone;
- case CullMode::Front:
- return MTLCullModeFront;
- case CullMode::Back:
- return MTLCullModeBack;
- }
- }
- static MTLBlendFactor Convert(DstBlendFactor factor, bool usedualsrc)
- {
- // clang-format off
- switch (factor)
- {
- case DstBlendFactor::Zero: return MTLBlendFactorZero;
- case DstBlendFactor::One: return MTLBlendFactorOne;
- case DstBlendFactor::SrcClr: return MTLBlendFactorSourceColor;
- case DstBlendFactor::InvSrcClr: return MTLBlendFactorOneMinusSourceColor;
- case DstBlendFactor::SrcAlpha: return usedualsrc ? MTLBlendFactorSource1Alpha
- : MTLBlendFactorSourceAlpha;
- case DstBlendFactor::InvSrcAlpha: return usedualsrc ? MTLBlendFactorOneMinusSource1Alpha
- : MTLBlendFactorOneMinusSourceAlpha;
- case DstBlendFactor::DstAlpha: return MTLBlendFactorDestinationAlpha;
- case DstBlendFactor::InvDstAlpha: return MTLBlendFactorOneMinusDestinationAlpha;
- }
- // clang-format on
- }
- static MTLBlendFactor Convert(SrcBlendFactor factor, bool usedualsrc)
- {
- // clang-format off
- switch (factor)
- {
- case SrcBlendFactor::Zero: return MTLBlendFactorZero;
- case SrcBlendFactor::One: return MTLBlendFactorOne;
- case SrcBlendFactor::DstClr: return MTLBlendFactorDestinationColor;
- case SrcBlendFactor::InvDstClr: return MTLBlendFactorOneMinusDestinationColor;
- case SrcBlendFactor::SrcAlpha: return usedualsrc ? MTLBlendFactorSource1Alpha
- : MTLBlendFactorSourceAlpha;
- case SrcBlendFactor::InvSrcAlpha: return usedualsrc ? MTLBlendFactorOneMinusSource1Alpha
- : MTLBlendFactorOneMinusSourceAlpha;
- case SrcBlendFactor::DstAlpha: return MTLBlendFactorDestinationAlpha;
- case SrcBlendFactor::InvDstAlpha: return MTLBlendFactorOneMinusDestinationAlpha;
- }
- // clang-format on
- }
- class Metal::ObjectCache::Internal
- {
- public:
- using StoredPipeline = std::pair<MRCOwned<id<MTLRenderPipelineState>>, PipelineReflection>;
- /// Holds only the things that are actually used in a Metal pipeline
- struct PipelineID
- {
- struct VertexAttribute
- {
- // Just hold the things that might differ while using the same shader
- // (Really only a thing for ubershaders)
- u8 offset : 6;
- u8 components : 2;
- VertexAttribute() = default;
- explicit VertexAttribute(AttributeFormat format)
- : offset(format.offset), components(format.components - 1)
- {
- if (!format.enable)
- offset = 0x3F; // Set it to something unlikely
- }
- };
- template <size_t N>
- static void CopyAll(std::array<VertexAttribute, N>& output,
- const std::array<AttributeFormat, N>& input)
- {
- for (size_t i = 0; i < N; ++i)
- output[i] = VertexAttribute(input[i]);
- }
- PipelineID(const AbstractPipelineConfig& cfg)
- {
- memset(this, 0, sizeof(*this));
- if (const NativeVertexFormat* v = cfg.vertex_format)
- {
- const PortableVertexDeclaration& decl = v->GetVertexDeclaration();
- v_stride = v->GetVertexStride();
- v_position = VertexAttribute(decl.position);
- CopyAll(v_normals, decl.normals);
- CopyAll(v_colors, decl.colors);
- CopyAll(v_texcoords, decl.texcoords);
- v_posmtx = VertexAttribute(decl.posmtx);
- }
- vertex_shader = static_cast<const Shader*>(cfg.vertex_shader);
- fragment_shader = static_cast<const Shader*>(cfg.pixel_shader);
- framebuffer.color_texture_format = cfg.framebuffer_state.color_texture_format.Value();
- framebuffer.depth_texture_format = cfg.framebuffer_state.depth_texture_format.Value();
- framebuffer.samples = cfg.framebuffer_state.samples.Value();
- framebuffer.additional_color_attachment_count =
- cfg.framebuffer_state.additional_color_attachment_count.Value();
- blend.colorupdate = cfg.blending_state.colorupdate.Value();
- blend.alphaupdate = cfg.blending_state.alphaupdate.Value();
- if (cfg.blending_state.blendenable)
- {
- // clang-format off
- blend.blendenable = true;
- blend.usedualsrc = cfg.blending_state.usedualsrc.Value();
- blend.srcfactor = cfg.blending_state.srcfactor.Value();
- blend.dstfactor = cfg.blending_state.dstfactor.Value();
- blend.srcfactoralpha = cfg.blending_state.srcfactoralpha.Value();
- blend.dstfactoralpha = cfg.blending_state.dstfactoralpha.Value();
- blend.subtract = cfg.blending_state.subtract.Value();
- blend.subtractAlpha = cfg.blending_state.subtractAlpha.Value();
- // clang-format on
- }
- if (cfg.usage != AbstractPipelineUsage::GXUber)
- {
- if (cfg.rasterization_state.primitive == PrimitiveType::Points)
- is_points = true;
- else if (cfg.rasterization_state.primitive == PrimitiveType::Lines)
- is_lines = true;
- }
- }
- PipelineID() { memset(this, 0, sizeof(*this)); }
- PipelineID(const PipelineID& other) { memcpy(this, &other, sizeof(*this)); }
- PipelineID& operator=(const PipelineID& other)
- {
- memcpy(this, &other, sizeof(*this));
- return *this;
- }
- bool operator<(const PipelineID& other) const
- {
- return memcmp(this, &other, sizeof(*this)) < 0;
- }
- bool operator==(const PipelineID& other) const
- {
- return memcmp(this, &other, sizeof(*this)) == 0;
- }
- u8 v_stride;
- VertexAttribute v_position;
- std::array<VertexAttribute, 3> v_normals;
- std::array<VertexAttribute, 2> v_colors;
- std::array<VertexAttribute, 8> v_texcoords;
- VertexAttribute v_posmtx;
- const Shader* vertex_shader;
- const Shader* fragment_shader;
- union
- {
- BlendingState blend;
- // Throw extras in bits we don't otherwise use
- BitField<30, 1, bool, u32> is_points;
- BitField<31, 1, bool, u32> is_lines;
- };
- FramebufferState framebuffer;
- };
- std::mutex m_mtx;
- std::condition_variable m_cv;
- std::map<PipelineID, StoredPipeline> m_pipelines;
- std::map<const Shader*, std::vector<PipelineID>> m_shaders;
- std::array<u32, 3> m_pipeline_counter;
- StoredPipeline CreatePipeline(const AbstractPipelineConfig& config)
- {
- @autoreleasepool
- {
- ASSERT(!config.geometry_shader);
- auto desc = MRCTransfer([MTLRenderPipelineDescriptor new]);
- [desc setVertexFunction:static_cast<const Shader*>(config.vertex_shader)->GetShader()];
- [desc setFragmentFunction:static_cast<const Shader*>(config.pixel_shader)->GetShader()];
- if (config.usage == AbstractPipelineUsage::GXUber)
- [desc setLabel:[NSString stringWithFormat:@"GX Uber Pipeline %d", m_pipeline_counter[0]++]];
- else if (config.usage == AbstractPipelineUsage::GX)
- [desc setLabel:[NSString stringWithFormat:@"GX Pipeline %d", m_pipeline_counter[1]++]];
- else
- [desc setLabel:[NSString stringWithFormat:@"Utility Pipeline %d", m_pipeline_counter[2]++]];
- if (config.vertex_format)
- [desc setVertexDescriptor:static_cast<const VertexFormat*>(config.vertex_format)->Get()];
- RasterizationState rs = config.rasterization_state;
- if (config.usage != AbstractPipelineUsage::GXUber)
- [desc setInputPrimitiveTopology:GetClass(rs.primitive)];
- MTLRenderPipelineColorAttachmentDescriptor* color0 =
- [[desc colorAttachments] objectAtIndexedSubscript:0];
- BlendingState bs = config.blending_state;
- MTLColorWriteMask mask = MTLColorWriteMaskNone;
- if (bs.colorupdate)
- mask |= MTLColorWriteMaskRed | MTLColorWriteMaskGreen | MTLColorWriteMaskBlue;
- if (bs.alphaupdate)
- mask |= MTLColorWriteMaskAlpha;
- [color0 setWriteMask:mask];
- if (bs.blendenable)
- {
- // clang-format off
- [color0 setBlendingEnabled:YES];
- [color0 setSourceRGBBlendFactor: Convert(bs.srcfactor, bs.usedualsrc)];
- [color0 setSourceAlphaBlendFactor: Convert(bs.srcfactoralpha, bs.usedualsrc)];
- [color0 setDestinationRGBBlendFactor: Convert(bs.dstfactor, bs.usedualsrc)];
- [color0 setDestinationAlphaBlendFactor:Convert(bs.dstfactoralpha, bs.usedualsrc)];
- [color0 setRgbBlendOperation: bs.subtract ? MTLBlendOperationReverseSubtract : MTLBlendOperationAdd];
- [color0 setAlphaBlendOperation:bs.subtractAlpha ? MTLBlendOperationReverseSubtract : MTLBlendOperationAdd];
- // clang-format on
- }
- FramebufferState fs = config.framebuffer_state;
- if (fs.color_texture_format == AbstractTextureFormat::Undefined &&
- fs.depth_texture_format == AbstractTextureFormat::Undefined)
- {
- // Intel HD 4000's Metal driver asserts if you try to make one of these
- PanicAlertFmt("Attempted to create pipeline with no render targets!");
- }
- [desc setRasterSampleCount:fs.samples];
- [color0 setPixelFormat:Util::FromAbstract(fs.color_texture_format)];
- if (u32 cnt = fs.additional_color_attachment_count)
- {
- for (u32 i = 0; i < cnt; i++)
- [[desc colorAttachments] setObject:color0 atIndexedSubscript:i + 1];
- }
- [desc setDepthAttachmentPixelFormat:Util::FromAbstract(fs.depth_texture_format)];
- if (Util::HasStencil(fs.depth_texture_format))
- [desc setStencilAttachmentPixelFormat:Util::FromAbstract(fs.depth_texture_format)];
- NSError* err = nullptr;
- MTLRenderPipelineReflection* reflection = nullptr;
- id<MTLRenderPipelineState> pipe =
- [g_device newRenderPipelineStateWithDescriptor:desc
- options:MTLPipelineOptionArgumentInfo
- reflection:&reflection
- error:&err];
- if (err)
- {
- static int counter;
- std::string filename = VideoBackendBase::BadShaderFilename("pipeline", counter++);
- FILE* file = fopen(filename.c_str(), "w");
- if (file)
- {
- fmt::println(file, "=============== Error ===============");
- fmt::println(file, "{}", [[err localizedDescription] UTF8String]);
- fmt::println(file, "============== Pipeline =============");
- fmt::println(file, "VS: {}", [[[desc vertexFunction] label] UTF8String]);
- fmt::println(file, "PS: {}", [[[desc fragmentFunction] label] UTF8String]);
- fmt::println(file, "Color Format: {}", static_cast<u32>(fs.color_texture_format.Value()));
- fmt::println(file, "Depth Format: {}", static_cast<u32>(fs.depth_texture_format.Value()));
- fmt::println(file, "Sample Count: {}", fs.samples);
- if (u32 cnt = fs.additional_color_attachment_count)
- fmt::println(file, "Additional Color Attachments: {}", cnt);
- if (bs.colorupdate && bs.alphaupdate)
- fmt::println(file, "Write Color, Alpha");
- else if (bs.colorupdate)
- fmt::println(file, "Write Color");
- else if (bs.alphaupdate)
- fmt::println(file, "Write Alpha");
- else
- fmt::println(file, "Write None");
- if (bs.blendenable)
- {
- auto print_blend = [file](const char* name, SrcBlendFactor src, DstBlendFactor dst,
- bool subtract) {
- if (subtract)
- fmt::println(file, "{}: dst * {} - src * {}", name, dst, src);
- else
- fmt::println(file, "{}: src * {} + dst * {}", name, src, dst);
- };
- print_blend("Color Blend", bs.srcfactor, bs.dstfactor, bs.subtract);
- print_blend("Alpha Blend", bs.srcfactoralpha, bs.dstfactoralpha, bs.subtractAlpha);
- fmt::println(file, "Blend Dual Source: {}", bs.usedualsrc ? "true" : "false");
- }
- else
- {
- fmt::println(file, "Blend Disabled");
- }
- if (const Shader* vs = static_cast<const Shader*>(config.vertex_shader))
- {
- fmt::println(file, "========= Vertex Shader MSL =========");
- fmt::println(file, "{}", vs->GetMSL());
- }
- if (const Shader* ps = static_cast<const Shader*>(config.pixel_shader))
- {
- fmt::println(file, "========== Pixel Shader MSL =========");
- fmt::println(file, "{}", ps->GetMSL());
- }
- fclose(file);
- }
- std::string file_msg = file ? fmt::format("Details were written to {}", filename) :
- "Failed to write detailed info";
- PanicAlertFmt("Failed to compile pipeline for {} and {}: {}\n{}",
- [[[desc vertexFunction] label] UTF8String],
- [[[desc fragmentFunction] label] UTF8String],
- [[err localizedDescription] UTF8String], file_msg);
- return std::make_pair(nullptr, PipelineReflection());
- }
- return std::make_pair(MRCTransfer(pipe), PipelineReflection(reflection));
- }
- }
- StoredPipeline GetOrCreatePipeline(const AbstractPipelineConfig& config)
- {
- std::unique_lock<std::mutex> lock(m_mtx);
- PipelineID pid(config);
- auto it = m_pipelines.find(pid);
- if (it != m_pipelines.end())
- {
- while (!it->second.first && !it->second.second.textures)
- m_cv.wait(lock); // Wait for whoever's already compiling this
- return it->second;
- }
- // Reserve the spot now, so other threads know we're making it
- it = m_pipelines.insert({pid, {nullptr, PipelineReflection()}}).first;
- lock.unlock();
- StoredPipeline pipe = CreatePipeline(config);
- lock.lock();
- if (pipe.first)
- it->second = pipe;
- else
- it->second.second.textures = 1; // Abuse this as a "failed to create pipeline" flag
- m_shaders[pid.vertex_shader].push_back(pid);
- m_shaders[pid.fragment_shader].push_back(pid);
- lock.unlock();
- m_cv.notify_all(); // Wake up anyone who might be waiting
- return pipe;
- }
- void ShaderDestroyed(const Shader* shader)
- {
- std::lock_guard<std::mutex> lock(m_mtx);
- auto it = m_shaders.find(shader);
- if (it == m_shaders.end())
- return;
- // It's unlikely, but if a shader is destroyed, a new one could be made with the same address
- // (Also, we know it won't be used anymore, so there's no reason to keep these around)
- for (const PipelineID& pid : it->second)
- m_pipelines.erase(pid);
- m_shaders.erase(it);
- }
- };
- std::unique_ptr<AbstractPipeline>
- Metal::ObjectCache::CreatePipeline(const AbstractPipelineConfig& config)
- {
- Internal::StoredPipeline pipeline = m_internal->GetOrCreatePipeline(config);
- if (!pipeline.first)
- return nullptr;
- return std::make_unique<Pipeline>(config, std::move(pipeline.first), pipeline.second,
- Convert(config.rasterization_state.primitive),
- Convert(config.rasterization_state.cullmode),
- config.depth_state, config.usage);
- }
- void Metal::ObjectCache::ShaderDestroyed(const Shader* shader)
- {
- m_internal->ShaderDestroyed(shader);
- }
|