From c7b7d1f71f89994d78bcc3b7563fb027ad47e056 Mon Sep 17 00:00:00 2001 From: Vincent Lejeune Date: Mon, 28 Sep 2015 22:51:32 +0200 Subject: [PATCH] Common: Move generic vertex buffer code from d3d12 backend --- rpcs3/Emu/RSX/Common/BufferUtils.cpp | 242 +++++++++++++++++++++++++ rpcs3/Emu/RSX/Common/BufferUtils.h | 40 +++++ rpcs3/Emu/RSX/D3D12/D3D12Buffer.cpp | 257 +-------------------------- rpcs3/Emu/RSX/GCM.h | 15 ++ rpcs3/emucore.vcxproj | 4 +- rpcs3/emucore.vcxproj.filters | 8 +- 6 files changed, 313 insertions(+), 253 deletions(-) create mode 100644 rpcs3/Emu/RSX/Common/BufferUtils.cpp create mode 100644 rpcs3/Emu/RSX/Common/BufferUtils.h diff --git a/rpcs3/Emu/RSX/Common/BufferUtils.cpp b/rpcs3/Emu/RSX/Common/BufferUtils.cpp new file mode 100644 index 0000000000..757add8e20 --- /dev/null +++ b/rpcs3/Emu/RSX/Common/BufferUtils.cpp @@ -0,0 +1,242 @@ +#include "stdafx.h" +#include "BufferUtils.h" + + +#define MIN2(x, y) ((x) < (y)) ? (x) : (y) +#define MAX2(x, y) ((x) > (y)) ? (x) : (y) + + +inline +bool overlaps(const std::pair &range1, const std::pair &range2) +{ + return !(range1.second < range2.first || range2.second < range1.first); +} + +std::vector FormatVertexData(const RSXVertexData *m_vertex_data, size_t *vertex_data_size, size_t base_offset) +{ + std::vector Result; + for (size_t i = 0; i < 32; ++i) + { + const RSXVertexData &vertexData = m_vertex_data[i]; + if (!vertexData.IsEnabled()) continue; + + size_t elementCount = vertex_data_size[i] / (vertexData.size * vertexData.GetTypeSize()); + // If there is a single element, stride is 0, use the size of element instead + size_t stride = vertexData.stride; + size_t elementSize = vertexData.GetTypeSize(); + std::pair range = std::make_pair(vertexData.addr + base_offset, vertexData.addr + base_offset + elementSize * vertexData.size + (elementCount - 1) * stride - 1); + bool isMerged = false; + + for (VertexBufferFormat &vbf : Result) + { + if (overlaps(vbf.range, range) && vbf.stride == stride) + { + // Extend buffer if necessary + vbf.range.first = MIN2(vbf.range.first, range.first); + vbf.range.second = MAX2(vbf.range.second, range.second); + vbf.elementCount = MAX2(vbf.elementCount, elementCount); + + vbf.attributeId.push_back(i); + isMerged = true; + break; + } + } + if (isMerged) + continue; + VertexBufferFormat newRange = { range, std::vector{ i }, elementCount, stride }; + Result.emplace_back(newRange); + } + return Result; +} + +void uploadVertexData(const VertexBufferFormat &vbf, const RSXVertexData *vertexData, size_t baseOffset, void* bufferMap) +{ + for (int vertex = 0; vertex < vbf.elementCount; vertex++) + { + for (size_t attributeId : vbf.attributeId) + { + if (!vertexData[attributeId].addr) + { + memcpy(bufferMap, vertexData[attributeId].data.data(), vertexData[attributeId].data.size()); + continue; + } + size_t offset = (size_t)vertexData[attributeId].addr + baseOffset - vbf.range.first; + size_t tsize = vertexData[attributeId].GetTypeSize(); + size_t size = vertexData[attributeId].size; + auto src = vm::get_ptr(vertexData[attributeId].addr + (u32)baseOffset + (u32)vbf.stride * vertex); + char* dst = (char*)bufferMap + offset + vbf.stride * vertex; + + switch (tsize) + { + case 1: + { + memcpy(dst, src, size); + break; + } + + case 2: + { + const u16* c_src = (const u16*)src; + u16* c_dst = (u16*)dst; + for (u32 j = 0; j < size; ++j) *c_dst++ = _byteswap_ushort(*c_src++); + break; + } + + case 4: + { + const u32* c_src = (const u32*)src; + u32* c_dst = (u32*)dst; + for (u32 j = 0; j < size; ++j) *c_dst++ = _byteswap_ulong(*c_src++); + break; + } + } + } + } +} + +template +void expandIndexedTriangleFan(DstType *dst, const SrcType *src, size_t indexCount) +{ + IndexType *typedDst = reinterpret_cast(dst); + const IndexType *typedSrc = reinterpret_cast(src); + for (unsigned i = 0; i < indexCount - 2; i++) + { + typedDst[3 * i] = typedSrc[0]; + typedDst[3 * i + 1] = typedSrc[i + 2 - 1]; + typedDst[3 * i + 2] = typedSrc[i + 2]; + } +} + +template +void expandIndexedQuads(DstType *dst, const SrcType *src, size_t indexCount) +{ + IndexType *typedDst = reinterpret_cast(dst); + const IndexType *typedSrc = reinterpret_cast(src); + for (unsigned i = 0; i < indexCount / 4; i++) + { + // First triangle + typedDst[6 * i] = typedSrc[4 * i]; + typedDst[6 * i + 1] = typedSrc[4 * i + 1]; + typedDst[6 * i + 2] = typedSrc[4 * i + 2]; + // Second triangle + typedDst[6 * i + 3] = typedSrc[4 * i + 2]; + typedDst[6 * i + 4] = typedSrc[4 * i + 3]; + typedDst[6 * i + 5] = typedSrc[4 * i]; + } +} + +// Only handle quads and triangle fan now +bool isNativePrimitiveMode(unsigned m_draw_mode) +{ + switch (m_draw_mode) + { + default: + case CELL_GCM_PRIMITIVE_POINTS: + case CELL_GCM_PRIMITIVE_LINES: + case CELL_GCM_PRIMITIVE_LINE_LOOP: + case CELL_GCM_PRIMITIVE_LINE_STRIP: + case CELL_GCM_PRIMITIVE_TRIANGLES: + case CELL_GCM_PRIMITIVE_TRIANGLE_STRIP: + case CELL_GCM_PRIMITIVE_QUAD_STRIP: + case CELL_GCM_PRIMITIVE_POLYGON: + return true; + case CELL_GCM_PRIMITIVE_TRIANGLE_FAN: + case CELL_GCM_PRIMITIVE_QUADS: + return false; + } +} + +size_t getIndexCount(unsigned m_draw_mode, unsigned initial_index_count) +{ + // Index count + if (isNativePrimitiveMode(m_draw_mode)) + return initial_index_count; + + switch (m_draw_mode) + { + case CELL_GCM_PRIMITIVE_TRIANGLE_FAN: + return (initial_index_count - 2) * 3; + case CELL_GCM_PRIMITIVE_QUADS: + return (6 * initial_index_count) / 4; + default: + return 0; + } +} + + +void uploadIndexData(unsigned m_draw_mode, unsigned index_type, void* indexBuffer, void* bufferMap, unsigned element_count) +{ + if (indexBuffer != nullptr) + { + switch (m_draw_mode) + { + case CELL_GCM_PRIMITIVE_POINTS: + case CELL_GCM_PRIMITIVE_LINES: + case CELL_GCM_PRIMITIVE_LINE_LOOP: + case CELL_GCM_PRIMITIVE_LINE_STRIP: + case CELL_GCM_PRIMITIVE_TRIANGLES: + case CELL_GCM_PRIMITIVE_TRIANGLE_STRIP: + case CELL_GCM_PRIMITIVE_QUAD_STRIP: + case CELL_GCM_PRIMITIVE_POLYGON: + { + size_t indexSize = (index_type == CELL_GCM_DRAW_INDEX_ARRAY_TYPE_32) ? 4 : 2; + memcpy(bufferMap, indexBuffer, indexSize * element_count); + return; + } + case CELL_GCM_PRIMITIVE_TRIANGLE_FAN: + switch (index_type) + { + case CELL_GCM_DRAW_INDEX_ARRAY_TYPE_32: + expandIndexedTriangleFan(bufferMap, indexBuffer, element_count); + return; + case CELL_GCM_DRAW_INDEX_ARRAY_TYPE_16: + expandIndexedTriangleFan(bufferMap, indexBuffer, element_count); + return; + default: + abort(); + return; + } + case CELL_GCM_PRIMITIVE_QUADS: + switch (index_type) + { + case CELL_GCM_DRAW_INDEX_ARRAY_TYPE_32: + expandIndexedQuads(bufferMap, indexBuffer, element_count); + return; + case CELL_GCM_DRAW_INDEX_ARRAY_TYPE_16: + expandIndexedQuads(bufferMap, indexBuffer, element_count); + return; + default: + abort(); + return; + } + } + } + else + { + unsigned short *typedDst = static_cast(bufferMap); + switch (m_draw_mode) + { + case CELL_GCM_PRIMITIVE_TRIANGLE_FAN: + for (unsigned i = 0; i < (element_count - 2); i++) + { + typedDst[3 * i] = 0; + typedDst[3 * i + 1] = i + 2 - 1; + typedDst[3 * i + 2] = i + 2; + } + return; + case CELL_GCM_PRIMITIVE_QUADS: + for (unsigned i = 0; i < element_count / 4; i++) + { + // First triangle + typedDst[6 * i] = 4 * i; + typedDst[6 * i + 1] = 4 * i + 1; + typedDst[6 * i + 2] = 4 * i + 2; + // Second triangle + typedDst[6 * i + 3] = 4 * i + 2; + typedDst[6 * i + 4] = 4 * i + 3; + typedDst[6 * i + 5] = 4 * i; + } + return; + } + } +} \ No newline at end of file diff --git a/rpcs3/Emu/RSX/Common/BufferUtils.h b/rpcs3/Emu/RSX/Common/BufferUtils.h new file mode 100644 index 0000000000..bdb46b22c4 --- /dev/null +++ b/rpcs3/Emu/RSX/Common/BufferUtils.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include "Emu/Memory/vm.h" +#include "../RSXThread.h" + + +struct VertexBufferFormat +{ + std::pair range; + std::vector attributeId; + size_t elementCount; + size_t stride; +}; + + +/* + * Detect buffer containing interleaved vertex attribute. + * This minimizes memory upload size. + */ +std::vector FormatVertexData(const RSXVertexData *m_vertex_data, size_t *vertex_data_size, size_t base_offset); + +/* + * Write vertex attributes to bufferMap, swapping data as required. + */ +void uploadVertexData(const VertexBufferFormat &vbf, const RSXVertexData *vertexData, size_t baseOffset, void* bufferMap); + +/* + * If primitive mode is not supported and need to be emulated (using an index buffer) returns false. + */ +bool isNativePrimitiveMode(unsigned m_draw_mode); + +/* + * Returns a fixed index count for emulated primitive, otherwise returns initial_index_count + */ +size_t getIndexCount(unsigned m_draw_mode, unsigned initial_index_count); + +/* + * Write index information to bufferMap + */ +void uploadIndexData(unsigned m_draw_mode, unsigned index_type, void* indexBuffer, void* bufferMap, unsigned element_count); \ No newline at end of file diff --git a/rpcs3/Emu/RSX/D3D12/D3D12Buffer.cpp b/rpcs3/Emu/RSX/D3D12/D3D12Buffer.cpp index e50d3802eb..c9042de107 100644 --- a/rpcs3/Emu/RSX/D3D12/D3D12Buffer.cpp +++ b/rpcs3/Emu/RSX/D3D12/D3D12Buffer.cpp @@ -5,6 +5,7 @@ #include "D3D12GSRender.h" #include "d3dx12.h" +#include "../Common/BufferUtils.h" const int g_vertexCount = 32; @@ -90,14 +91,6 @@ DXGI_FORMAT getFormat(u8 type, u8 size) } } -struct VertexBufferFormat -{ - std::pair range; - std::vector attributeId; - size_t elementCount; - size_t stride; -}; - static std::vector getIALayout(ID3D12Device *device, const std::vector &vertexBufferFormat, const RSXVertexData *m_vertex_data, size_t baseOffset) { @@ -122,89 +115,9 @@ std::vector getIALayout(ID3D12Device *device, const st return result; } -template -void expandIndexedTriangleFan(DstType *dst, const SrcType *src, size_t indexCount) -{ - IndexType *typedDst = reinterpret_cast(dst); - const IndexType *typedSrc = reinterpret_cast(src); - for (unsigned i = 0; i < indexCount - 2; i++) - { - typedDst[3 * i] = typedSrc[0]; - typedDst[3 * i + 1] = typedSrc[i + 2 - 1]; - typedDst[3 * i + 2] = typedSrc[i + 2]; - } -} - -template -void expandIndexedQuads(DstType *dst, const SrcType *src, size_t indexCount) -{ - IndexType *typedDst = reinterpret_cast(dst); - const IndexType *typedSrc = reinterpret_cast(src); - for (unsigned i = 0; i < indexCount / 4; i++) - { - // First triangle - typedDst[6 * i] = typedSrc[4 * i]; - typedDst[6 * i + 1] = typedSrc[4 * i + 1]; - typedDst[6 * i + 2] = typedSrc[4 * i + 2]; - // Second triangle - typedDst[6 * i + 3] = typedSrc[4 * i + 2]; - typedDst[6 * i + 4] = typedSrc[4 * i + 3]; - typedDst[6 * i + 5] = typedSrc[4 * i]; - } -} - - // D3D12GS member handling buffers - -#define MIN2(x, y) ((x) < (y)) ? (x) : (y) -#define MAX2(x, y) ((x) > (y)) ? (x) : (y) - -static -bool overlaps(const std::pair &range1, const std::pair &range2) -{ - return !(range1.second < range2.first || range2.second < range1.first); -} - -static -std::vector FormatVertexData(const RSXVertexData *m_vertex_data, size_t *vertex_data_size, size_t base_offset) -{ - std::vector Result; - for (size_t i = 0; i < 32; ++i) - { - const RSXVertexData &vertexData = m_vertex_data[i]; - if (!vertexData.IsEnabled()) continue; - - size_t elementCount = vertex_data_size[i] / (vertexData.size * vertexData.GetTypeSize()); - // If there is a single element, stride is 0, use the size of element instead - size_t stride = vertexData.stride; - size_t elementSize = vertexData.GetTypeSize(); - std::pair range = std::make_pair(vertexData.addr + base_offset, vertexData.addr + base_offset + elementSize * vertexData.size + (elementCount - 1) * stride - 1); - bool isMerged = false; - - for (VertexBufferFormat &vbf : Result) - { - if (overlaps(vbf.range, range) && vbf.stride == stride) - { - // Extend buffer if necessary - vbf.range.first = MIN2(vbf.range.first, range.first); - vbf.range.second = MAX2(vbf.range.second, range.second); - vbf.elementCount = MAX2(vbf.elementCount, elementCount); - - vbf.attributeId.push_back(i); - isMerged = true; - break; - } - } - if (isMerged) - continue; - VertexBufferFormat newRange = { range, std::vector{ i }, elementCount, stride }; - Result.emplace_back(newRange); - } - return Result; -} - /** * Suballocate a new vertex buffer with attributes from vbf using vertexIndexHeap as storage heap. */ @@ -221,63 +134,11 @@ D3D12_GPU_VIRTUAL_ADDRESS createVertexBuffer(const VertexBufferFormat &vbf, cons void *buffer; ThrowIfFailed(vertexIndexHeap.m_heap->Map(0, &CD3DX12_RANGE(heapOffset, heapOffset + subBufferSize), (void**)&buffer)); void *bufferMap = (char*)buffer + heapOffset; - for (int vertex = 0; vertex < vbf.elementCount; vertex++) - { - for (size_t attributeId : vbf.attributeId) - { - if (!vertexData[attributeId].addr) - { - memcpy(bufferMap, vertexData[attributeId].data.data(), vertexData[attributeId].data.size()); - continue; - } - size_t offset = (size_t)vertexData[attributeId].addr + baseOffset - vbf.range.first; - size_t tsize = vertexData[attributeId].GetTypeSize(); - size_t size = vertexData[attributeId].size; - auto src = vm::get_ptr(vertexData[attributeId].addr + (u32)baseOffset + (u32)vbf.stride * vertex); - char* dst = (char*)bufferMap + offset + vbf.stride * vertex; - - switch (tsize) - { - case 1: - { - memcpy(dst, src, size); - break; - } - - case 2: - { - const u16* c_src = (const u16*)src; - u16* c_dst = (u16*)dst; - for (u32 j = 0; j < size; ++j) *c_dst++ = _byteswap_ushort(*c_src++); - break; - } - - case 4: - { - const u32* c_src = (const u32*)src; - u32* c_dst = (u32*)dst; - for (u32 j = 0; j < size; ++j) *c_dst++ = _byteswap_ulong(*c_src++); - break; - } - } - } - } - + uploadVertexData(vbf, vertexData, baseOffset, bufferMap); vertexIndexHeap.m_heap->Unmap(0, &CD3DX12_RANGE(heapOffset, heapOffset + subBufferSize)); return vertexIndexHeap.m_heap->GetGPUVirtualAddress() + heapOffset; } -static bool -isContained(const std::vector > &ranges, const std::pair &range) -{ - for (auto &r : ranges) - { - if (r == range) - return true; - } - return false; -} - std::vector D3D12GSRender::UploadVertexBuffers(bool indexed_draw) { std::vector result; @@ -310,29 +171,9 @@ std::vector D3D12GSRender::UploadVertexBuffers(bool in D3D12_INDEX_BUFFER_VIEW D3D12GSRender::uploadIndexBuffers(bool indexed_draw) { D3D12_INDEX_BUFFER_VIEW indexBufferView = {}; - // Only handle quads and triangle fan now - bool forcedIndexBuffer = false; - switch (m_draw_mode - 1) - { - default: - case GL_POINTS: - case GL_LINES: - case GL_LINE_LOOP: - case GL_LINE_STRIP: - case GL_TRIANGLES: - case GL_TRIANGLE_STRIP: - case GL_QUAD_STRIP: - case GL_POLYGON: - forcedIndexBuffer = false; - break; - case GL_TRIANGLE_FAN: - case GL_QUADS: - forcedIndexBuffer = true; - break; - } // No need for index buffer - if (!indexed_draw && !forcedIndexBuffer) + if (!indexed_draw && isNativePrimitiveMode(m_draw_mode)) { m_renderingInfo.m_indexed = false; m_renderingInfo.m_count = m_draw_array_count; @@ -366,35 +207,10 @@ D3D12_INDEX_BUFFER_VIEW D3D12GSRender::uploadIndexBuffers(bool indexed_draw) } // Index count - if (indexed_draw && !forcedIndexBuffer) - m_renderingInfo.m_count = m_indexed_array.m_data.size() / indexSize; - else if (indexed_draw && forcedIndexBuffer) - { - switch (m_draw_mode - 1) - { - case GL_TRIANGLE_FAN: - m_renderingInfo.m_count = (m_indexed_array.m_data.size() - 2) * 3; - break; - case GL_QUADS: - m_renderingInfo.m_count = 6 * m_indexed_array.m_data.size() / (4 * indexSize); - break; - } - } - else - { - switch (m_draw_mode - 1) - { - case GL_TRIANGLE_FAN: - m_renderingInfo.m_count = (m_draw_array_count - 2) * 3; - break; - case GL_QUADS: - m_renderingInfo.m_count = m_draw_array_count * 6 / 4; - break; - } - } + m_renderingInfo.m_count = getIndexCount(m_draw_mode, indexed_draw ? (u32)(m_indexed_array.m_data.size() / indexSize) : m_draw_array_count); // Base vertex - if (!indexed_draw && forcedIndexBuffer) + if (!indexed_draw && isNativePrimitiveMode(m_draw_mode)) m_renderingInfo.m_baseVertex = m_draw_array_first; else m_renderingInfo.m_baseVertex = 0; @@ -408,70 +224,9 @@ D3D12_INDEX_BUFFER_VIEW D3D12GSRender::uploadIndexBuffers(bool indexed_draw) void *buffer; ThrowIfFailed(m_vertexIndexData.m_heap->Map(0, &CD3DX12_RANGE(heapOffset, heapOffset + subBufferSize), (void**)&buffer)); void *bufferMap = (char*)buffer + heapOffset; - if (indexed_draw && !forcedIndexBuffer) - streamBuffer(bufferMap, m_indexed_array.m_data.data(), subBufferSize); - else if (indexed_draw && forcedIndexBuffer) - { - // Only quads supported now - switch (m_draw_mode - 1) - { - case GL_TRIANGLE_FAN: - switch (m_indexed_array.m_type) - { - case CELL_GCM_DRAW_INDEX_ARRAY_TYPE_32: - expandIndexedTriangleFan(bufferMap, m_indexed_array.m_data.data(), m_indexed_array.m_data.size() / 4); - break; - case CELL_GCM_DRAW_INDEX_ARRAY_TYPE_16: - expandIndexedTriangleFan(bufferMap, m_indexed_array.m_data.data(), m_indexed_array.m_data.size() / 2); - break; - } - break; - case GL_QUADS: - switch (m_indexed_array.m_type) - { - case CELL_GCM_DRAW_INDEX_ARRAY_TYPE_32: - expandIndexedQuads(bufferMap, m_indexed_array.m_data.data(), m_indexed_array.m_data.size() / 4); - break; - case CELL_GCM_DRAW_INDEX_ARRAY_TYPE_16: - expandIndexedQuads(bufferMap, m_indexed_array.m_data.data(), m_indexed_array.m_data.size() / 2); - break; - } - break; - } - } - else - { - unsigned short *typedDst = static_cast(bufferMap); - switch (m_draw_mode - 1) - { - case GL_TRIANGLE_FAN: - for (unsigned i = 0; i < (m_draw_array_count - 2); i++) - { - typedDst[3 * i] = 0; - typedDst[3 * i + 1] = i + 2 - 1; - typedDst[3 * i + 2] = i + 2; - } - break; - case GL_QUADS: - for (unsigned i = 0; i < m_draw_array_count / 4; i++) - { - // First triangle - typedDst[6 * i] = 4 * i; - typedDst[6 * i + 1] = 4 * i + 1; - typedDst[6 * i + 2] = 4 * i + 2; - // Second triangle - typedDst[6 * i + 3] = 4 * i + 2; - typedDst[6 * i + 4] = 4 * i + 3; - typedDst[6 * i + 5] = 4 * i; - } - break; - } - - } + uploadIndexData(m_draw_mode, m_indexed_array.m_type, indexed_draw ? m_indexed_array.m_data.data() : nullptr, bufferMap, indexed_draw ? (u32)(m_indexed_array.m_data.size() / indexSize) : m_draw_array_count); m_vertexIndexData.m_heap->Unmap(0, &CD3DX12_RANGE(heapOffset, heapOffset + subBufferSize)); - m_timers.m_bufferUploadSize += subBufferSize; - indexBufferView.SizeInBytes = (UINT)subBufferSize; indexBufferView.BufferLocation = m_vertexIndexData.m_heap->GetGPUVirtualAddress() + heapOffset; return indexBufferView; diff --git a/rpcs3/Emu/RSX/GCM.h b/rpcs3/Emu/RSX/GCM.h index 366f81cbf1..bddafa0380 100644 --- a/rpcs3/Emu/RSX/GCM.h +++ b/rpcs3/Emu/RSX/GCM.h @@ -260,6 +260,21 @@ enum }; +// GCM Primitive +enum +{ + CELL_GCM_PRIMITIVE_POINTS = 1, + CELL_GCM_PRIMITIVE_LINES = 2, + CELL_GCM_PRIMITIVE_LINE_LOOP = 3, + CELL_GCM_PRIMITIVE_LINE_STRIP = 4, + CELL_GCM_PRIMITIVE_TRIANGLES = 5, + CELL_GCM_PRIMITIVE_TRIANGLE_STRIP = 6, + CELL_GCM_PRIMITIVE_TRIANGLE_FAN = 7, + CELL_GCM_PRIMITIVE_QUADS = 8, + CELL_GCM_PRIMITIVE_QUAD_STRIP = 9, + CELL_GCM_PRIMITIVE_POLYGON = 10, +}; + // GCM Reports enum { diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index eb021b8964..d89cf351c9 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -60,6 +60,7 @@ + @@ -530,10 +531,11 @@ + - + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index c46abfddf6..c7734ec744 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -992,6 +992,9 @@ Emu\GPU\RSX\Common + + Emu\GPU\RSX\Common + @@ -1885,7 +1888,10 @@ Emu\GPU\RSX\D3D12 - + + Emu\GPU\RSX\Common + + Emu\GPU\RSX\Common