| /* | |
| * Copyright 2025 Rive | |
| */ | |
| #include "rive/renderer/d3d12/render_context_d3d12_impl.hpp" | |
| #include "rive/renderer/d3d/d3d_constants.hpp" | |
| #ifdef RIVE_CANVAS | |
| #include "rive/renderer/render_canvas.hpp" | |
| #include "rive/renderer/ore/ore_context_d3d12.hpp" | |
| #endif | |
| #include "rive/profiler/profiler_macros.h" | |
| #include "rive/renderer/d3d/d3d_utils.hpp" | |
| // needed for root sig and heap constants | |
| #include "shaders/d3d/root.sig" | |
| #include <sstream> | |
| #include <vector> | |
| #include <D3DCompiler.h> | |
| // this is defined here instead of root_sig becaise the gpu does not care about | |
| // the number of rtvs. This is gradient, tess, feather atlas, and color | |
| static constexpr UINT NUM_RTV_HEAP_DESCRIPTORS = 4; | |
| namespace rive::gpu | |
| { | |
| static constexpr D3D12_FILTER filter_for_sampler_options(ImageFilter option) | |
| { | |
| switch (option) | |
| { | |
| case ImageFilter::nearest: | |
| return D3D12_FILTER_MIN_MAG_MIP_POINT; | |
| case ImageFilter::bilinear: | |
| return D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT; | |
| } | |
| RIVE_UNREACHABLE(); | |
| return D3D12_FILTER_MIN_MAG_MIP_LINEAR; | |
| }; | |
| static constexpr D3D12_TEXTURE_ADDRESS_MODE address_mode_for_sampler_option( | |
| ImageWrap option) | |
| { | |
| switch (option) | |
| { | |
| case ImageWrap::clamp: | |
| return D3D12_TEXTURE_ADDRESS_MODE_CLAMP; | |
| case ImageWrap::repeat: | |
| return D3D12_TEXTURE_ADDRESS_MODE_WRAP; | |
| case ImageWrap::mirror: | |
| return D3D12_TEXTURE_ADDRESS_MODE_MIRROR; | |
| } | |
| RIVE_UNREACHABLE(); | |
| return D3D12_TEXTURE_ADDRESS_MODE_CLAMP; | |
| } | |
| void RenderContextD3D12Impl::blitSubRect(ID3D12GraphicsCommandList* cmdList, | |
| D3D12Texture* dst, | |
| D3D12Texture* src, | |
| const IAABB& rect) | |
| { | |
| D3D12_BOX updateBox = { | |
| static_cast<UINT>(rect.left), | |
| static_cast<UINT>(rect.top), | |
| 0, | |
| static_cast<UINT>(rect.right), | |
| static_cast<UINT>(rect.bottom), | |
| 1, | |
| }; | |
| D3D12_TEXTURE_COPY_LOCATION dstLoc; | |
| dstLoc.SubresourceIndex = 0; | |
| dstLoc.pResource = dst->resource(); | |
| dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; | |
| D3D12_TEXTURE_COPY_LOCATION srcLoc; | |
| srcLoc.SubresourceIndex = 0; | |
| srcLoc.pResource = src->resource(); | |
| srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; | |
| m_resourceManager->transition(cmdList, | |
| src, | |
| D3D12_RESOURCE_STATE_COPY_SOURCE); | |
| m_resourceManager->transition(cmdList, dst, D3D12_RESOURCE_STATE_COPY_DEST); | |
| cmdList->CopyTextureRegion(&dstLoc, | |
| updateBox.left, | |
| updateBox.top, | |
| 0, | |
| &srcLoc, | |
| &updateBox); | |
| // we don't untransition here because we don't | |
| // know what we want it to be, so instead we just always transition before | |
| // use | |
| } | |
| class TextureD3D12Impl : public Texture | |
| { | |
| public: | |
| TextureD3D12Impl(rcp<D3D12Texture> externalTexture) : | |
| Texture(static_cast<uint32_t>(externalTexture->width()), | |
| static_cast<uint32_t>(externalTexture->height())), | |
| m_gpuTexture(std::move(externalTexture)) | |
| {} | |
| TextureD3D12Impl(D3D12ResourceManager* manager, | |
| UINT width, | |
| UINT height, | |
| UINT mipLevel, | |
| GPUTextureFormat format, | |
| const uint8_t imageData[], | |
| bool usesCommandList) : | |
| Texture(width, height) | |
| { | |
| DXGI_FORMAT d3dFormat = d3d_utils::convert_format(format); | |
| // Always create in COMMON. Both upload paths drive the texture | |
| // through the copy command list which uses enhanced barriers, and | |
| // enhanced barriers require COMMON layout (LEGACY_COPY_DEST is | |
| // rejected as INCOMPATIBLE_BARRIER_LAYOUT). The copy itself | |
| // promotes COMMON→COPY_DEST implicitly. | |
| std::ignore = usesCommandList; | |
| m_gpuTexture = manager->make2DTexture(width, | |
| height, | |
| mipLevel, | |
| d3dFormat, | |
| D3D12_RESOURCE_FLAG_NONE, | |
| D3D12_RESOURCE_STATE_COMMON); | |
| if (format == GPUTextureFormat::bc7) | |
| { | |
| // imageData contains pre-compressed BC7 blocks, level 0 first, | |
| // levels packed tight (no inter-level padding). Copy each level | |
| // into its placed-subresource slot in the upload buffer. | |
| auto desc = m_gpuTexture->resource()->GetDesc(); | |
| m_subresourceFootprints.resize(mipLevel); | |
| std::vector<UINT> numRows(mipLevel); | |
| std::vector<UINT64> rowSizeInBytes(mipLevel); | |
| UINT64 totalBytes = 0; | |
| manager->device()->GetCopyableFootprints( | |
| &desc, | |
| 0, // First subresource | |
| mipLevel, // Number of mips | |
| 0, // Base offset | |
| m_subresourceFootprints.data(), // One footprint per mip | |
| numRows.data(), | |
| rowSizeInBytes.data(), | |
| &totalBytes); | |
| m_uploadBuffer = manager->makeUploadBuffer( | |
| static_cast<UINT>(totalBytes) + | |
| D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT); | |
| uint8_t* dst = reinterpret_cast<uint8_t*>(m_uploadBuffer->map()); | |
| const uint8_t* src = imageData; | |
| for (UINT mip = 0; mip < mipLevel; ++mip) | |
| { | |
| const auto& fp = m_subresourceFootprints[mip].Footprint; | |
| // RowPitch is padded to D3D12_TEXTURE_DATA_PITCH_ALIGNMENT | |
| // (256). Source rows are tight (rowSizeInBytes). Copy one | |
| // block-row at a time so we don't overflow the upload slot | |
| // and don't read past the source mip. | |
| uint8_t* dstMip = dst + m_subresourceFootprints[mip].Offset; | |
| const UINT64 srcRowBytes = rowSizeInBytes[mip]; | |
| const UINT rows = numRows[mip]; | |
| for (UINT row = 0; row < rows; ++row) | |
| { | |
| memcpy(dstMip + row * fp.RowPitch, | |
| src + row * srcRowBytes, | |
| srcRowBytes); | |
| } | |
| src += srcRowBytes * rows; | |
| } | |
| } | |
| else if (format == GPUTextureFormat::rgba32) | |
| { | |
| D3D12_SUBRESOURCE_DATA srcData; | |
| srcData.pData = imageData; | |
| srcData.RowPitch = width * 4; | |
| srcData.SlicePitch = srcData.RowPitch * height; | |
| UINT numRows; | |
| UINT64 rowSizeInBtes; | |
| UINT64 totalBytes; | |
| auto desc = m_gpuTexture->resource()->GetDesc(); | |
| SNAME_D3D12_OBJECT(m_gpuTexture, "riveImage"); | |
| manager->device()->GetCopyableFootprints(&desc, | |
| 0, | |
| 1, | |
| 0, | |
| &m_uploadFootprint, | |
| &numRows, | |
| &rowSizeInBtes, | |
| &totalBytes); | |
| m_uploadBuffer = manager->makeUploadBuffer( | |
| static_cast<UINT>(totalBytes) + | |
| D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT); | |
| D3D12_MEMCPY_DEST DestData = { | |
| m_uploadBuffer->map(), | |
| m_uploadFootprint.Footprint.RowPitch, | |
| SIZE_T(m_uploadFootprint.Footprint.RowPitch * numRows)}; | |
| MemcpySubresource(&DestData, | |
| &srcData, | |
| static_cast<SIZE_T>(rowSizeInBtes), | |
| numRows, | |
| m_uploadFootprint.Footprint.Depth); | |
| } | |
| else | |
| { | |
| assert(!"unsupported format"); | |
| } | |
| } | |
| D3D12Texture* synchronize(ID3D12GraphicsCommandList* copyList, | |
| ID3D12GraphicsCommandList* cmdList, | |
| D3D12ResourceManager* manager) const | |
| { | |
| if (m_uploadBuffer) | |
| { | |
| if (!m_subresourceFootprints.empty()) | |
| { | |
| // BC7: copy each mip level to its own subresource. | |
| for (UINT mip = 0; | |
| mip < static_cast<UINT>(m_subresourceFootprints.size()); | |
| ++mip) | |
| { | |
| const CD3DX12_TEXTURE_COPY_LOCATION dst( | |
| m_gpuTexture->resource(), | |
| mip); | |
| const CD3DX12_TEXTURE_COPY_LOCATION src( | |
| m_uploadBuffer->resource(), | |
| m_subresourceFootprints[mip]); | |
| copyList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr); | |
| } | |
| } | |
| else | |
| { | |
| // RGBA: single subresource copy. | |
| const CD3DX12_TEXTURE_COPY_LOCATION dst( | |
| m_gpuTexture->resource(), | |
| 0); | |
| const CD3DX12_TEXTURE_COPY_LOCATION src( | |
| m_uploadBuffer->resource(), | |
| m_uploadFootprint); | |
| copyList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr); | |
| } | |
| manager->transition(cmdList, | |
| m_gpuTexture.get(), | |
| D3D12_RESOURCE_STATE_GENERIC_READ); | |
| m_uploadBuffer = nullptr; | |
| } | |
| return m_gpuTexture.get(); | |
| } | |
| D3D12Texture* resource() const { return m_gpuTexture.get(); } | |
| void* nativeHandle() const override | |
| { | |
| return static_cast<void*>(m_gpuTexture.get()); | |
| } | |
| private: | |
| mutable rcp<D3D12Buffer> m_uploadBuffer; | |
| rcp<D3D12Texture> m_gpuTexture; | |
| D3D12_PLACED_SUBRESOURCE_FOOTPRINT | |
| m_uploadFootprint; // RGBA path (single subresource) | |
| std::vector<D3D12_PLACED_SUBRESOURCE_FOOTPRINT> | |
| m_subresourceFootprints; // BC7 path (per-mip) | |
| }; | |
| class RenderBufferD3D12Impl | |
| : public LITE_RTTI_OVERRIDE(RenderBuffer, RenderBufferD3D12Impl) | |
| { | |
| public: | |
| RenderBufferD3D12Impl(D3D12ResourceManager* manager, | |
| RenderBufferType renderBufferType, | |
| RenderBufferFlags renderBufferFlags, | |
| size_t sizeInBytes) : | |
| lite_rtti_override(renderBufferType, renderBufferFlags, sizeInBytes), | |
| m_gpuBuffer(manager->makeBuffer(static_cast<UINT>(sizeInBytes), | |
| D3D12_RESOURCE_FLAG_NONE, | |
| D3D12_RESOURCE_STATE_COMMON)) | |
| { | |
| m_uploadBuffer = | |
| manager->makeUploadBuffer(static_cast<UINT>(sizeInBytes)); | |
| if (enums::is_flag_set(flags(), | |
| RenderBufferFlags::mappedOnceAtInitialization)) | |
| { | |
| mappedOnceMempry = m_uploadBuffer->map(); | |
| } | |
| } | |
| D3D12Buffer* sync(ID3D12GraphicsCommandList* cmdList) | |
| { | |
| if (needsUpload) | |
| { | |
| needsUpload = false; | |
| assert(m_uploadBuffer->sizeInBytes() == m_gpuBuffer->sizeInBytes()); | |
| cmdList->CopyBufferRegion(m_gpuBuffer->resource(), | |
| 0, | |
| m_uploadBuffer->resource(), | |
| 0, | |
| m_uploadBuffer->sizeInBytes()); | |
| const auto barrier = CD3DX12_RESOURCE_BARRIER::Transition( | |
| m_gpuBuffer->resource(), | |
| D3D12_RESOURCE_STATE_COPY_DEST, | |
| D3D12_RESOURCE_STATE_COMMON); | |
| cmdList->ResourceBarrier(1, &barrier); | |
| if (enums::is_flag_set( | |
| flags(), | |
| RenderBufferFlags::mappedOnceAtInitialization)) | |
| { | |
| m_uploadBuffer = nullptr; | |
| } | |
| } | |
| return m_gpuBuffer.get(); | |
| } | |
| protected: | |
| void* onMap() override | |
| { | |
| assert(m_uploadBuffer); | |
| needsUpload = true; | |
| if (enums::is_flag_set(flags(), | |
| RenderBufferFlags::mappedOnceAtInitialization)) | |
| { | |
| assert(mappedOnceMempry); | |
| return mappedOnceMempry; | |
| } | |
| else | |
| { | |
| return m_uploadBuffer->map(); | |
| } | |
| } | |
| void onUnmap() override | |
| { | |
| assert(m_uploadBuffer); | |
| if (enums::is_flag_set(flags(), | |
| RenderBufferFlags::mappedOnceAtInitialization)) | |
| { | |
| assert(mappedOnceMempry); | |
| m_uploadBuffer->unmap(); | |
| mappedOnceMempry = nullptr; | |
| } | |
| else | |
| { | |
| m_uploadBuffer->unmap(); | |
| } | |
| } | |
| private: | |
| rcp<D3D12Buffer> m_uploadBuffer; | |
| const rcp<D3D12Buffer> m_gpuBuffer; | |
| void* mappedOnceMempry = nullptr; | |
| bool needsUpload = false; | |
| }; | |
| RenderTargetD3D12::RenderTargetD3D12(RenderContextD3D12Impl* impl, | |
| int width, | |
| int height) : | |
| RenderTarget(width, height), | |
| m_manager(impl->manager()), | |
| m_gpuSupportsTypedUAVLoadStore( | |
| impl->d3dCapabilities().supportsTypedUAVLoadStore) | |
| {} | |
| void RenderTargetD3D12::setTargetTexture(rcp<D3D12Texture> tex) | |
| { | |
| if (tex && tex->resource()) | |
| { | |
| D3D12_RESOURCE_DESC desc = tex->desc(); | |
| #ifdef DEBUG | |
| assert(desc.Width == width()); | |
| assert(desc.Height == height()); | |
| assert(desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM || | |
| desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM || | |
| desc.Format == DXGI_FORMAT_R8G8B8A8_TYPELESS || | |
| desc.Format == DXGI_FORMAT_B8G8R8A8_TYPELESS); | |
| #endif | |
| m_targetTextureSupportsUAV = | |
| (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) && | |
| m_gpuSupportsTypedUAVLoadStore; | |
| m_targetFormat = desc.Format; | |
| } | |
| else | |
| { | |
| m_targetTextureSupportsUAV = false; | |
| } | |
| m_targetTexture = std::move(tex); | |
| SNAME_D3D12_OBJECT(m_targetTexture, "color"); | |
| } | |
| void RenderTargetD3D12::setTargetTexture(ComPtr<ID3D12Resource> tex) | |
| { | |
| if (tex) | |
| { | |
| D3D12_RESOURCE_DESC desc = tex->GetDesc(); | |
| #ifdef DEBUG | |
| assert(desc.Width == width()); | |
| assert(desc.Height == height()); | |
| assert(desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM || | |
| desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM || | |
| desc.Format == DXGI_FORMAT_R8G8B8A8_TYPELESS || | |
| desc.Format == DXGI_FORMAT_B8G8R8A8_TYPELESS); | |
| #endif | |
| m_targetTextureSupportsUAV = | |
| (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) && | |
| m_gpuSupportsTypedUAVLoadStore; | |
| m_targetFormat = desc.Format; | |
| } | |
| else | |
| { | |
| m_targetTextureSupportsUAV = false; | |
| } | |
| m_targetTexture = | |
| m_manager->makeExternalTexture(tex, D3D12_RESOURCE_STATE_PRESENT); | |
| SNAME_D3D12_OBJECT(m_targetTexture, "colorExternal"); | |
| } | |
| D3D12Texture* RenderTargetD3D12::offscreenTexture() | |
| { | |
| if (!m_offscreenTexture) | |
| { | |
| m_offscreenTexture = | |
| m_manager->make2DTexture(width(), | |
| height(), | |
| 1, | |
| DXGI_FORMAT_R8G8B8A8_TYPELESS, | |
| D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, | |
| D3D12_RESOURCE_STATE_UNORDERED_ACCESS, | |
| D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS); | |
| SNAME_D3D12_OBJECT(m_offscreenTexture, "offscreenColor"); | |
| } | |
| return m_offscreenTexture.get(); | |
| } | |
| void RenderTargetD3D12::markTargetUAV(D3D12DescriptorHeap* heap) | |
| { | |
| assert(m_targetTexture); | |
| D3D12Texture* targetTexture = m_targetTexture.get(); | |
| if (!m_targetTextureSupportsUAV) | |
| { | |
| targetTexture = offscreenTexture(); | |
| } | |
| if (auto& uavTexture = | |
| m_targetTextureSupportsUAV ? m_targetTexture : m_offscreenTexture) | |
| { | |
| DXGI_FORMAT targetUavFormat; | |
| switch (m_targetFormat) | |
| { | |
| case DXGI_FORMAT_R8G8B8A8_UNORM: | |
| case DXGI_FORMAT_R8G8B8A8_TYPELESS: | |
| targetUavFormat = m_gpuSupportsTypedUAVLoadStore | |
| ? DXGI_FORMAT_R8G8B8A8_UNORM | |
| : DXGI_FORMAT_R32_UINT; | |
| break; | |
| case DXGI_FORMAT_B8G8R8A8_UNORM: | |
| case DXGI_FORMAT_B8G8R8A8_TYPELESS: | |
| targetUavFormat = m_gpuSupportsTypedUAVLoadStore | |
| ? DXGI_FORMAT_B8G8R8A8_UNORM | |
| : DXGI_FORMAT_R32_UINT; | |
| break; | |
| default: | |
| RIVE_UNREACHABLE(); | |
| } | |
| heap->markUavToIndex(m_manager->device(), | |
| targetTexture, | |
| targetUavFormat, | |
| ATOMIC_COLOR_HEAP_OFFSET); | |
| } | |
| } | |
| void RenderTargetD3D12::markClipUAV(D3D12DescriptorHeap* heap) | |
| { | |
| if (m_clipTexture == nullptr) | |
| { | |
| m_clipTexture = | |
| m_manager->make2DTexture(width(), | |
| height(), | |
| 1, | |
| DXGI_FORMAT_R32_UINT, | |
| D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, | |
| D3D12_RESOURCE_STATE_UNORDERED_ACCESS); | |
| SNAME_D3D12_OBJECT(m_clipTexture, "clip"); | |
| } | |
| assert(m_clipTexture); | |
| heap->markUavToIndex(m_manager->device(), | |
| m_clipTexture.get(), | |
| DXGI_FORMAT_R32_UINT, | |
| ATOMIC_CLIP_HEAP_OFFSET); | |
| } | |
| void RenderTargetD3D12::markScratchColorUAV(D3D12DescriptorHeap* heap) | |
| { | |
| // checks and makes texture | |
| if (!m_scratchColorTexture) | |
| { | |
| m_scratchColorTexture = | |
| m_manager->make2DTexture(width(), | |
| height(), | |
| 1, | |
| DXGI_FORMAT_R8G8B8A8_TYPELESS, | |
| D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, | |
| D3D12_RESOURCE_STATE_UNORDERED_ACCESS, | |
| D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS); | |
| SNAME_D3D12_OBJECT(m_scratchColorTexture, "scratchColor"); | |
| } | |
| assert(m_scratchColorTexture); | |
| heap->markUavToIndex(m_manager->device(), | |
| m_scratchColorTexture.get(), | |
| m_gpuSupportsTypedUAVLoadStore | |
| ? DXGI_FORMAT_R8G8B8A8_UNORM | |
| : DXGI_FORMAT_R32_UINT, | |
| ATOMIC_SCRATCH_COLOR_HEAP_OFFSET); | |
| } | |
| void RenderTargetD3D12::markCoverageUAV(D3D12DescriptorHeap* heap) | |
| { | |
| if (m_coverageTexture == nullptr) | |
| { | |
| m_coverageTexture = | |
| m_manager->make2DTexture(width(), | |
| height(), | |
| 1, | |
| DXGI_FORMAT_R32_UINT, | |
| D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, | |
| D3D12_RESOURCE_STATE_UNORDERED_ACCESS); | |
| SNAME_D3D12_OBJECT(m_coverageTexture, "coverage"); | |
| } | |
| heap->markUavToIndex(m_manager->device(), | |
| m_coverageTexture.get(), | |
| DXGI_FORMAT_R32_UINT, | |
| ATOMIC_COVERAGE_HEAP_OFFSET); | |
| } | |
| std::unique_ptr<RenderContext> RenderContextD3D12Impl::MakeContext( | |
| ComPtr<ID3D12Device> device, | |
| ID3D12GraphicsCommandList* copyCommandList, | |
| const D3DContextOptions& contextOptions) | |
| { | |
| D3DCapabilities d3dCapabilities; | |
| D3D12_FEATURE_DATA_D3D12_OPTIONS d3d12Options; | |
| if (SUCCEEDED(device->CheckFeatureSupport( | |
| D3D12_FEATURE_D3D12_OPTIONS, | |
| &d3d12Options, | |
| sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS)))) | |
| { | |
| d3dCapabilities.supportsRasterizerOrderedViews = | |
| d3d12Options.ROVsSupported && !contextOptions.isIntelArc; | |
| if (d3d12Options.TypedUAVLoadAdditionalFormats) | |
| { | |
| // TypedUAVLoadAdditionalFormats is true. Now check if we can | |
| // both load and store all formats used by Rive (currently only | |
| // RGBA8 and BGRA8): | |
| // https://learn.microsoft.com/en-us/windows/win32/direct3d11/typed-unordered-access-view-loads. | |
| auto check_typed_uav_load = [device](DXGI_FORMAT format) { | |
| D3D12_FEATURE_DATA_FORMAT_SUPPORT d3d12Format{}; | |
| d3d12Format.Format = format; | |
| if (SUCCEEDED(device->CheckFeatureSupport( | |
| D3D12_FEATURE_FORMAT_SUPPORT, | |
| &d3d12Format, | |
| sizeof(d3d12Format)))) | |
| { | |
| constexpr UINT loadStoreFlags = | |
| D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD | | |
| D3D12_FORMAT_SUPPORT2_UAV_TYPED_STORE; | |
| return (d3d12Format.Support2 & loadStoreFlags) == | |
| loadStoreFlags; | |
| } | |
| return false; | |
| }; | |
| d3dCapabilities.supportsTypedUAVLoadStore = | |
| check_typed_uav_load(DXGI_FORMAT_R8G8B8A8_UNORM) && | |
| check_typed_uav_load(DXGI_FORMAT_B8G8R8A8_UNORM); | |
| // Check if we can use HLSL minimum precision types (e.g. min16int) | |
| d3dCapabilities.supportsMin16Precision = | |
| d3d12Options.MinPrecisionSupport & | |
| D3D12_SHADER_MIN_PRECISION_SUPPORT_16_BIT; | |
| } | |
| } | |
| if (contextOptions.disableRasterizerOrderedViews) | |
| { | |
| d3dCapabilities.supportsRasterizerOrderedViews = false; | |
| } | |
| if (contextOptions.disableTypedUAVLoadStore) | |
| { | |
| d3dCapabilities.supportsTypedUAVLoadStore = false; | |
| } | |
| d3dCapabilities.isIntel = contextOptions.isIntel; | |
| auto renderContextImpl = std::unique_ptr<RenderContextD3D12Impl>( | |
| new RenderContextD3D12Impl(device, | |
| copyCommandList, | |
| d3dCapabilities, | |
| contextOptions.shaderCompilationMode)); | |
| return std::make_unique<RenderContext>(std::move(renderContextImpl)); | |
| } | |
| RenderContextD3D12Impl::RenderContextD3D12Impl( | |
| ComPtr<ID3D12Device> device, | |
| ID3D12GraphicsCommandList* copyCommandList, | |
| const D3DCapabilities& capabilities, | |
| ShaderCompilationMode shaderCompilationMode) : | |
| m_device(device), | |
| m_capabilities(capabilities), | |
| m_pipelineManager(device, capabilities, shaderCompilationMode), | |
| m_resourceManager(make_rcp<D3D12ResourceManager>(device)), | |
| m_flushUniformBufferPool(m_resourceManager, sizeof(FlushUniforms)), | |
| m_pathBufferPool(m_resourceManager), | |
| m_paintBufferPool(m_resourceManager), | |
| m_paintAuxBufferPool(m_resourceManager), | |
| m_contourBufferPool(m_resourceManager), | |
| m_gradSpanBufferPool(m_resourceManager), | |
| m_tessSpanBufferPool(m_resourceManager), | |
| m_triangleBufferPool(m_resourceManager), | |
| m_imageDrawInstanceBufferPool(m_resourceManager, sizeof(ImageDrawInstance)), | |
| // this is 64kb which is the smallest heap that can be sent to gpu | |
| m_srvUavCbvHeapPool(m_resourceManager, | |
| MAX_DESCRIPTOR_HEAPS_PER_FLUSH, | |
| D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, | |
| D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE), | |
| m_cpuSrvUavCbvHeapPool(m_resourceManager, | |
| NUM_SRV_UAV_HEAP_DESCRIPTORS, | |
| D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, | |
| D3D12_DESCRIPTOR_HEAP_FLAG_NONE), | |
| m_samplerHeapPool(m_resourceManager, | |
| MAX_DESCRIPTOR_SAMPLER_HEAPS_PER_FLUSH, | |
| D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, | |
| D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE) | |
| { | |
| m_platformFeatures.clipSpaceBottomUp = true; | |
| m_platformFeatures.framebufferBottomUp = false; | |
| m_platformFeatures.supportsRasterOrderingMode = | |
| m_capabilities.supportsRasterizerOrderedViews; | |
| m_platformFeatures.supportsAtomicMode = true; | |
| m_platformFeatures.maxTextureSize = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION; | |
| m_platformFeatures.supportsClipScissor = true; | |
| // BC1–BC7 are required at D3D feature level 11.0+. | |
| m_platformFeatures.supportsTextureCompressionBC = true; | |
| m_rtvHeap = m_resourceManager->makeHeap(NUM_RTV_HEAP_DESCRIPTORS, | |
| D3D12_DESCRIPTOR_HEAP_TYPE_RTV, | |
| D3D12_DESCRIPTOR_HEAP_FLAG_NONE); | |
| m_pipelineManager.compileGradientPipeline(); | |
| m_pipelineManager.compileTesselationPipeline(); | |
| m_pipelineManager.compileFeatherAtlasPipeline(); | |
| m_linearSampler.Filter = D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT; | |
| m_linearSampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; | |
| m_linearSampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; | |
| m_linearSampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; | |
| m_linearSampler.MipLODBias = 0.0f; | |
| m_linearSampler.MaxAnisotropy = 1; | |
| m_linearSampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; | |
| m_linearSampler.MinLOD = 0; | |
| m_linearSampler.MaxLOD = 0; | |
| for (int i = 0; i < ImageSampler::MAX_SAMPLER_PERMUTATIONS; ++i) | |
| { | |
| auto wrapX = ImageSampler::GetWrapXOptionFromKey(i); | |
| auto wrapY = ImageSampler::GetWrapYOptionFromKey(i); | |
| auto filter = ImageSampler::GetFilterOptionFromKey(i); | |
| m_imageSamplers[i].Filter = filter_for_sampler_options(filter); | |
| m_imageSamplers[i].AddressU = address_mode_for_sampler_option(wrapX); | |
| m_imageSamplers[i].AddressV = address_mode_for_sampler_option(wrapY); | |
| m_imageSamplers[i].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; | |
| m_imageSamplers[i].MipLODBias = 0.0f; | |
| m_imageSamplers[i].MaxAnisotropy = 1; | |
| m_imageSamplers[i].ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; | |
| m_imageSamplers[i].MinLOD = 0; | |
| // this should be D3D12_FLOAT32_MAX but currently we don't generate mips | |
| m_imageSamplers[i].MaxLOD = 0; | |
| } | |
| PatchVertex patchVertices[kPatchVertexBufferCount]; | |
| uint16_t patchIndices[kPatchIndexBufferCount]; | |
| GeneratePatchBufferData(patchVertices, patchIndices); | |
| // advance to ensure that safe frame does not equal current frame so that | |
| // our temp upload buffers do not get released automatically | |
| m_resourceManager->advanceFrameNumber(1, 0); | |
| m_pathPatchVertexBuffer = | |
| m_resourceManager->makeImmutableBuffer<>(copyCommandList, | |
| patchVertices); | |
| NAME_D3D12_OBJECT(m_pathPatchVertexBuffer); | |
| m_pathPatchIndexBuffer = | |
| m_resourceManager->makeImmutableBuffer<>(copyCommandList, patchIndices); | |
| NAME_D3D12_OBJECT(m_pathPatchIndexBuffer); | |
| m_tessSpanIndexBuffer = | |
| m_resourceManager->makeImmutableBuffer<>(copyCommandList, | |
| kTessSpanIndices); | |
| NAME_D3D12_OBJECT(m_tessSpanIndexBuffer); | |
| m_imageRectVertexBuffer = | |
| m_resourceManager->makeImmutableBuffer<>(copyCommandList, | |
| kImageRectVertices); | |
| NAME_D3D12_OBJECT(m_imageRectVertexBuffer); | |
| m_imageRectIndexBuffer = | |
| m_resourceManager->makeImmutableBuffer<>(copyCommandList, | |
| kImageRectIndices); | |
| NAME_D3D12_OBJECT(m_imageRectIndexBuffer); | |
| m_gaussianIntegralTexture = m_resourceManager->make1DTextureArray( | |
| GAUSSIAN_TABLE_SIZE, | |
| GAUSSIAN_INTEGRAL_TEXTURE_1D_ARRAY_LENGTH, | |
| 1, | |
| DXGI_FORMAT_R16_FLOAT, | |
| D3D12_RESOURCE_FLAG_NONE, | |
| D3D12_RESOURCE_STATE_COMMON); | |
| NAME_D3D12_OBJECT(m_gaussianIntegralTexture); | |
| auto featherUploadBuffer = m_resourceManager->makeUploadBuffer( | |
| sizeof(gpu::g_inverseGaussianIntegralTableF16) * 2); | |
| D3D12_SUBRESOURCE_DATA updateData; | |
| updateData.pData = g_gaussianIntegralTableF16; | |
| updateData.RowPitch = sizeof(g_gaussianIntegralTableF16); | |
| updateData.SlicePitch = updateData.RowPitch; | |
| UpdateSubresources(copyCommandList, | |
| m_gaussianIntegralTexture->resource(), | |
| featherUploadBuffer->resource(), | |
| 0, | |
| 0, | |
| 1, | |
| &updateData); | |
| D3D12_SUBRESOURCE_DATA updateData2; | |
| updateData2.pData = g_inverseGaussianIntegralTableF16; | |
| updateData2.RowPitch = sizeof(g_inverseGaussianIntegralTableF16); | |
| updateData2.SlicePitch = updateData.RowPitch; | |
| UpdateSubresources(copyCommandList, | |
| m_gaussianIntegralTexture->resource(), | |
| featherUploadBuffer->resource(), | |
| sizeof(gpu::g_gaussianIntegralTableF16), | |
| 1, | |
| 1, | |
| &updateData2); | |
| } | |
| rcp<RenderBuffer> RenderContextD3D12Impl::makeRenderBuffer( | |
| RenderBufferType type, | |
| RenderBufferFlags flags, | |
| size_t size) | |
| { | |
| return make_rcp<RenderBufferD3D12Impl>(m_resourceManager.get(), | |
| type, | |
| flags, | |
| size); | |
| } | |
| rcp<Texture> RenderContextD3D12Impl::makeImageTexture( | |
| uint32_t width, | |
| uint32_t height, | |
| uint32_t mipLevelCount, | |
| GPUTextureFormat format, | |
| const uint8_t imageDataRGBAPremul[], | |
| uint8_t /*blockWidth*/, | |
| uint8_t /*blockHeight*/, | |
| bool /*srgb*/, | |
| bool /*generateRemainingMips*/) | |
| { | |
| return make_rcp<TextureD3D12Impl>(m_resourceManager.get(), | |
| width, | |
| height, | |
| mipLevelCount, | |
| format, | |
| imageDataRGBAPremul, | |
| m_usesCopyCommandList); | |
| } | |
| rcp<Texture> RenderContextD3D12Impl::adoptImageTexture( | |
| rcp<D3D12Texture> imageTexture) | |
| { | |
| return make_rcp<TextureD3D12Impl>(std::move(imageTexture)); | |
| } | |
| rcp<Texture> RenderContextD3D12Impl::adoptImageTexture(ID3D12Resource* resource, | |
| uint32_t width, | |
| uint32_t height, | |
| DXGI_FORMAT viewFormat) | |
| { | |
| if (resource == nullptr || width == 0 || height == 0) | |
| { | |
| return nullptr; | |
| } | |
| // ComPtr adds a transient ref dropped when our wrapper destructs; the | |
| // real lifetime stays with the original owner (e.g. Unity). | |
| ComPtr<ID3D12Resource> comResource(resource); | |
| auto d3dTex = | |
| make_rcp<D3D12Texture>(m_resourceManager, | |
| std::move(comResource), | |
| D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); | |
| d3dTex->setSrvViewFormat(viewFormat); | |
| return adoptImageTexture(std::move(d3dTex)); | |
| } | |
| #ifdef RIVE_CANVAS | |
| void RenderContextD3D12Impl::ensureCanvasBacking(gpu::RenderCanvas* canvas) | |
| { | |
| if (canvas->isBacked()) | |
| { | |
| return; | |
| } | |
| uint32_t width = canvas->width(), height = canvas->height(); | |
| auto texture = | |
| manager()->make2DTexture(width, | |
| height, | |
| 1, | |
| DXGI_FORMAT_R8G8B8A8_UNORM, | |
| D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS | | |
| D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, | |
| D3D12_RESOURCE_STATE_COMMON); | |
| auto renderTarget = make_rcp<RenderTargetD3D12>(this, width, height); | |
| renderTarget->setTargetTexture(texture); | |
| canvas->setBacking(adoptImageTexture(std::move(texture)), | |
| std::move(renderTarget)); | |
| } | |
| std::unique_ptr<rive::ore::Context> RenderContextD3D12Impl::makeOreContext() | |
| { | |
| return rive::ore::ContextD3D12::Make(m_resourceManager, m_device.Get()); | |
| } | |
| #endif | |
| void rive::gpu::RenderContextD3D12Impl::resizeGradientTexture(uint32_t width, | |
| uint32_t height) | |
| { | |
| if (width == 0 || height == 0) | |
| { | |
| m_gradientTexture = nullptr; | |
| } | |
| else | |
| { | |
| m_gradientTexture = m_resourceManager->make2DTexture( | |
| width, | |
| height, | |
| 1, | |
| DXGI_FORMAT_R8G8B8A8_UNORM, | |
| D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET); | |
| // these can be done here since they arent recycled every frame | |
| m_rtvHeap->markRtvToIndex(m_device.Get(), | |
| m_gradientTexture.get(), | |
| GRAD_RTV_HEAP_OFFSET); | |
| SNAME_D3D12_OBJECT(m_gradientTexture, "gradient"); | |
| } | |
| } | |
| void rive::gpu::RenderContextD3D12Impl::resizeTessellationTexture( | |
| uint32_t width, | |
| uint32_t height) | |
| { | |
| if (width == 0 || height == 0) | |
| { | |
| m_tesselationTexture = nullptr; | |
| } | |
| else | |
| { | |
| m_tesselationTexture = m_resourceManager->make2DTexture( | |
| width, | |
| height, | |
| 1, | |
| DXGI_FORMAT_R32G32B32A32_UINT, | |
| D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET); | |
| // these can be done here since they arent recycled every frame | |
| m_rtvHeap->markRtvToIndex(m_device.Get(), | |
| m_tesselationTexture.get(), | |
| TESS_RTV_HEAP_OFFSET); | |
| SNAME_D3D12_OBJECT(m_tesselationTexture, "tesselation"); | |
| } | |
| } | |
| void rive::gpu::RenderContextD3D12Impl::resizeFeatherAtlasTexture( | |
| uint32_t width, | |
| uint32_t height) | |
| { | |
| if (width == 0 || height == 0) | |
| { | |
| m_featherAtlasTexture = nullptr; | |
| } | |
| else | |
| { | |
| D3D12_CLEAR_VALUE clear{DXGI_FORMAT_R16_FLOAT, {}}; | |
| m_featherAtlasTexture = m_resourceManager->make2DTexture( | |
| width, | |
| height, | |
| 1, | |
| DXGI_FORMAT_R16_FLOAT, | |
| D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, | |
| D3D12_RESOURCE_STATE_COMMON, | |
| D3D12_HEAP_FLAG_NONE, | |
| &clear); | |
| // these can be done here since they arent recycled every frame | |
| m_rtvHeap->markRtvToIndex(m_device.Get(), | |
| m_featherAtlasTexture.get(), | |
| FEATHER_ATLAS_RTV_HEAP_OFFSET); | |
| SNAME_D3D12_OBJECT(m_featherAtlasTexture, "featherAtlas"); | |
| } | |
| } | |
| // Internal struct to hold D3D12 canvas command buffer resources. | |
| // Allocated by makeCommandBuffer(), freed by commitCommandBuffer(). | |
| struct CanvasCommandBuffer | |
| { | |
| RenderContextD3D12Impl::CommandLists lists; | |
| ComPtr<ID3D12CommandAllocator> allocator; | |
| ComPtr<ID3D12GraphicsCommandList> commandList; | |
| }; | |
| void* RenderContextD3D12Impl::makeCommandBuffer() | |
| { | |
| if (m_canvasQueue == nullptr) | |
| { | |
| return nullptr; | |
| } | |
| auto* cb = new CanvasCommandBuffer(); | |
| HRESULT hr = | |
| m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, | |
| IID_PPV_ARGS(&cb->allocator)); | |
| if (FAILED(hr)) | |
| { | |
| delete cb; | |
| return nullptr; | |
| } | |
| hr = m_device->CreateCommandList(0, | |
| D3D12_COMMAND_LIST_TYPE_DIRECT, | |
| cb->allocator.Get(), | |
| nullptr, | |
| IID_PPV_ARGS(&cb->commandList)); | |
| if (FAILED(hr)) | |
| { | |
| delete cb; | |
| return nullptr; | |
| } | |
| cb->lists.copyComandList = nullptr; // use direct list for everything | |
| cb->lists.directComandList = cb->commandList.Get(); | |
| return &cb->lists; | |
| } | |
| void RenderContextD3D12Impl::commitCommandBuffer(void* commandBuffer) | |
| { | |
| if (commandBuffer == nullptr) | |
| { | |
| return; | |
| } | |
| // Recover the CanvasCommandBuffer from the CommandLists pointer. | |
| auto* lists = static_cast<CommandLists*>(commandBuffer); | |
| auto* cb = reinterpret_cast<CanvasCommandBuffer*>( | |
| reinterpret_cast<char*>(lists) - offsetof(CanvasCommandBuffer, lists)); | |
| cb->commandList->Close(); | |
| ID3D12CommandList* ppCommandLists[] = {cb->commandList.Get()}; | |
| m_canvasQueue->ExecuteCommandLists(1, ppCommandLists); | |
| // Wait for completion so the canvas texture is ready for compositing. | |
| ComPtr<ID3D12Fence> fence; | |
| m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)); | |
| m_canvasQueue->Signal(fence.Get(), 1); | |
| if (fence->GetCompletedValue() < 1) | |
| { | |
| HANDLE event = CreateEvent(nullptr, FALSE, FALSE, nullptr); | |
| fence->SetEventOnCompletion(1, event); | |
| WaitForSingleObject(event, INFINITE); | |
| CloseHandle(event); | |
| } | |
| delete cb; | |
| } | |
| void RenderContextD3D12Impl::prepareToFlush(uint64_t nextFrameNumber, | |
| uint64_t safeFrameNumber) | |
| { | |
| // These should all have gotten recycled at the end of the last frame. | |
| assert(m_flushUniformBuffer == nullptr); | |
| assert(m_pathBuffer == nullptr); | |
| assert(m_paintBuffer == nullptr); | |
| assert(m_paintAuxBuffer == nullptr); | |
| assert(m_contourBuffer == nullptr); | |
| assert(m_gradSpanBuffer == nullptr); | |
| assert(m_tessSpanBuffer == nullptr); | |
| assert(m_triangleBuffer == nullptr); | |
| assert(m_imageDrawInstanceBuffer == nullptr); | |
| assert(m_srvUavCbvHeap == nullptr); | |
| assert(m_cpuSrvUavCbvHeap == nullptr); | |
| assert(m_samplerHeap == nullptr); | |
| // Advance the context frame and delete resources that are no longer | |
| // referenced by in-flight command buffers. | |
| // When nextFrameNumber is 0 this is a canvas pre-pass flush that uses its | |
| // own command buffer (via makeCommandBuffer/commitCommandBuffer). Skip | |
| // advancing the frame number because the pre-pass executes synchronously | |
| // and doesn't participate in frame lifetime tracking. | |
| if (nextFrameNumber != 0) | |
| { | |
| m_resourceManager->advanceFrameNumber(nextFrameNumber, safeFrameNumber); | |
| } | |
| // Acquire buffers for the flush. | |
| m_flushUniformBuffer = m_flushUniformBufferPool.acquire(); | |
| m_pathBuffer = m_pathBufferPool.acquire(); | |
| m_paintBuffer = m_paintBufferPool.acquire(); | |
| m_paintAuxBuffer = m_paintAuxBufferPool.acquire(); | |
| m_contourBuffer = m_contourBufferPool.acquire(); | |
| m_gradSpanBuffer = m_gradSpanBufferPool.acquire(); | |
| m_tessSpanBuffer = m_tessSpanBufferPool.acquire(); | |
| m_triangleBuffer = m_triangleBufferPool.acquire(); | |
| m_imageDrawInstanceBuffer = m_imageDrawInstanceBufferPool.acquire(); | |
| m_srvUavCbvHeap = m_srvUavCbvHeapPool.acquire(); | |
| m_cpuSrvUavCbvHeap = m_cpuSrvUavCbvHeapPool.acquire(); | |
| m_samplerHeap = m_samplerHeapPool.acquire(); | |
| VNAME_D3D12_OBJECT(m_flushUniformBuffer); | |
| VNAME_D3D12_OBJECT(m_pathBuffer); | |
| VNAME_D3D12_OBJECT(m_paintBuffer); | |
| VNAME_D3D12_OBJECT(m_paintAuxBuffer); | |
| VNAME_D3D12_OBJECT(m_contourBuffer); | |
| VNAME_D3D12_OBJECT(m_gradSpanBuffer); | |
| VNAME_D3D12_OBJECT(m_tessSpanBuffer); | |
| VNAME_D3D12_OBJECT(m_triangleBuffer); | |
| VNAME_D3D12_OBJECT(m_imageDrawInstanceBuffer); | |
| m_isFirstFlushOfFrame = true; | |
| m_heapDescriptorOffset = 0; | |
| m_samplerHeapDescriptorOffset = IMAGE_SAMPLER_HEAP_OFFSET; | |
| m_lastDynamicSampler = ImageSampler::LinearClamp(); | |
| } | |
| void RenderContextD3D12Impl::flush(const FlushDescriptor& desc) | |
| { | |
| RIVE_PROF_GPUNAME_L(0, "RiveFlush"); | |
| CommandLists* commandLists = | |
| static_cast<CommandLists*>(desc.externalCommandBuffer); | |
| assert(commandLists); | |
| auto copyCmdList = commandLists->copyComandList; | |
| auto cmdList = commandLists->directComandList; | |
| // it's ok but less efficient to use one command list for everything | |
| // and a copy command list may not be guaranteed on certain platforms | |
| if (!copyCmdList) | |
| { | |
| copyCmdList = cmdList; | |
| m_usesCopyCommandList = false; | |
| } | |
| else | |
| { | |
| m_usesCopyCommandList = true; | |
| } | |
| assert(copyCmdList); | |
| assert(cmdList); | |
| // this assert doesn't take into account any potential images | |
| // there is no way to currently know how many we have though so we have to | |
| // just do this and assert the image index later | |
| if (m_heapDescriptorOffset + NUM_SRV_UAV_HEAP_DESCRIPTORS >= | |
| MAX_DESCRIPTOR_HEAPS_PER_FLUSH) | |
| { | |
| fprintf(stderr, | |
| "heap descriptor ran out of room ! heapOffset %i maxHeap %i\n", | |
| m_heapDescriptorOffset, | |
| MAX_DESCRIPTOR_HEAPS_PER_FLUSH); | |
| assert(false); | |
| return; | |
| } | |
| auto renderTarget = static_cast<RenderTargetD3D12*>(desc.renderTarget); | |
| auto width = renderTarget->width(); | |
| auto height = renderTarget->height(); | |
| auto targetTexture = renderTarget->targetTexture(); | |
| // offset for where to start writing image descriptors into the heap | |
| // on every frame but the first, that is just the number of dynamic | |
| // descriptors the first frame though we need to move it the entire length | |
| // of descriptors over | |
| UINT64 imageDescriptorOffset = NUM_DYNAMIC_SRV_HEAP_DESCRIPTORS; | |
| if (m_isFirstFlushOfFrame) | |
| { | |
| m_isFirstFlushOfFrame = false; | |
| // the -1 is because this size includes the image descriptor itself | |
| // note we could just use IMAGE_HEAP_OFFSET_START but i thought this | |
| // was more readable for someone who hasn't seen this code before | |
| imageDescriptorOffset = (NUM_SRV_UAV_HEAP_DESCRIPTORS - 1); | |
| // sync everything first logical flush, | |
| // copies are slow, so do it all at once | |
| m_flushUniformBuffer->sync(copyCmdList, 0); | |
| if (desc.pathCount > 0) | |
| { | |
| m_pathBuffer->sync(copyCmdList, 0); | |
| m_paintBuffer->sync(copyCmdList, 0); | |
| m_paintAuxBuffer->sync(copyCmdList, 0); | |
| } | |
| if (desc.contourCount > 0) | |
| { | |
| m_contourBuffer->sync(copyCmdList, 0); | |
| } | |
| if (desc.tessVertexSpanCount) | |
| { | |
| m_tessSpanBuffer->sync(copyCmdList, 0); | |
| } | |
| if (desc.gradSpanCount) | |
| { | |
| m_gradSpanBuffer->sync(copyCmdList, 0); | |
| } | |
| if (desc.hasTriangleVertices) | |
| { | |
| m_triangleBuffer->sync(copyCmdList, 0); | |
| } | |
| m_imageDrawInstanceBuffer->sync(copyCmdList, 0); | |
| // mark all UAVS, thse only need to happen the fist time since they | |
| // won't change per logical flush | |
| renderTarget->markClipUAV(m_cpuSrvUavCbvHeap.get()); | |
| renderTarget->markCoverageUAV(m_cpuSrvUavCbvHeap.get()); | |
| renderTarget->markTargetUAV(m_cpuSrvUavCbvHeap.get()); | |
| renderTarget->markScratchColorUAV(m_cpuSrvUavCbvHeap.get()); | |
| m_rtvHeap->markRtvToIndex(m_device.Get(), | |
| targetTexture, | |
| TARGET_RTV_HEAP_OFFSET); | |
| // mark all the texture srvs, these also only need to be done once per | |
| // flush since the texture aren't re created per logical flush. When | |
| // the source texture is not currently allocated we still need to write | |
| // a valid (null) descriptor — the cpu heap is acquired from a pool and | |
| // can retain stale descriptors from prior frames, which would trip | |
| // GPU-Based Validation (id=1042) when STATIC_SRV is bound. | |
| if (m_gradientTexture) | |
| { | |
| m_cpuSrvUavCbvHeap->markSrvToIndex(m_device.Get(), | |
| m_gradientTexture.get(), | |
| GRAD_IMAGE_HEAP_OFFSET); | |
| } | |
| else | |
| { | |
| m_cpuSrvUavCbvHeap->markNullTexture2DSrvToIndex( | |
| m_device.Get(), | |
| GRAD_IMAGE_HEAP_OFFSET, | |
| DXGI_FORMAT_R8G8B8A8_UNORM); | |
| } | |
| if (m_tesselationTexture) | |
| { | |
| m_cpuSrvUavCbvHeap->markSrvToIndex(m_device.Get(), | |
| m_tesselationTexture.get(), | |
| TESS_IMAGE_HEAP_OFFSET); | |
| } | |
| else | |
| { | |
| m_cpuSrvUavCbvHeap->markNullTexture2DSrvToIndex( | |
| m_device.Get(), | |
| TESS_IMAGE_HEAP_OFFSET, | |
| DXGI_FORMAT_R32G32B32A32_UINT); | |
| } | |
| if (m_featherAtlasTexture) | |
| { | |
| m_cpuSrvUavCbvHeap->markSrvToIndex(m_device.Get(), | |
| m_featherAtlasTexture.get(), | |
| FEATHER_ATLAS_IMAGE_HEAP_OFFSET); | |
| } | |
| else | |
| { | |
| m_cpuSrvUavCbvHeap->markNullTexture2DSrvToIndex( | |
| m_device.Get(), | |
| FEATHER_ATLAS_IMAGE_HEAP_OFFSET, | |
| DXGI_FORMAT_R16_FLOAT); | |
| } | |
| assert(m_gaussianIntegralTexture); | |
| m_cpuSrvUavCbvHeap->markSrvToIndex(m_device.Get(), | |
| m_gaussianIntegralTexture.get(), | |
| GAUSSIAN_INTEGRAL_IMAGE_HEAP_OFFSET); | |
| // do this only once. | |
| m_pipelineManager.setRootSig(cmdList); | |
| ID3D12DescriptorHeap* ppHeaps[] = {m_srvUavCbvHeap->heap(), | |
| m_samplerHeap->heap()}; | |
| // copy the static descriptors | |
| m_device->CopyDescriptorsSimple( | |
| NUM_STATIC_SRV_UAV_HEAP_DESCRIPTORS, | |
| m_srvUavCbvHeap->cpuHandleForUpload( | |
| STATIC_SRV_UAV_HEAP_DESCRIPTOPR_START), | |
| m_cpuSrvUavCbvHeap->cpuHandleForIndex( | |
| STATIC_SRV_UAV_HEAP_DESCRIPTOPR_START), | |
| D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); | |
| // this is extremely expensive as it causes a fence on some hardware | |
| // (nvidia) so we do this only once per flush | |
| // some api's may potentially expose a global heap to use, if so, we | |
| // should use that one instead. but that will have to be addressed once | |
| // we start integrating | |
| cmdList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps); | |
| // setup samplers, these don't change, | |
| // image sampler will eventually but for now keep it here | |
| m_samplerHeap->markSamplerToIndex(m_device.Get(), | |
| m_linearSampler, | |
| TESS_SAMPLER_HEAP_OFFSET); | |
| m_samplerHeap->markSamplerToIndex(m_device.Get(), | |
| m_linearSampler, | |
| GRAD_SAMPLER_HEAP_OFFSET); | |
| m_samplerHeap->markSamplerToIndex( | |
| m_device.Get(), | |
| m_linearSampler, | |
| GAUSSIAN_INTEGRAL_SAMPLER_HEAP_OFFSET); | |
| m_samplerHeap->markSamplerToIndex(m_device.Get(), | |
| m_linearSampler, | |
| FEATHER_ATLAS_SAMPLER_HEAP_OFFSET); | |
| // this SHOULD be m_mipSampler but we don't currently generate mips | |
| m_samplerHeap->markSamplerToIndex(m_device.Get(), | |
| m_linearSampler, | |
| IMAGE_SAMPLER_HEAP_OFFSET); | |
| cmdList->SetGraphicsRootDescriptorTable( | |
| STATIC_SRV_SIG_INDEX, | |
| m_srvUavCbvHeap->gpuHandleForIndex( | |
| STATIC_SRV_UAV_HEAP_DESCRIPTOPR_START)); | |
| cmdList->SetGraphicsRootDescriptorTable( | |
| UAV_SIG_INDEX, | |
| m_srvUavCbvHeap->gpuHandleForIndex(UAV_START_HEAP_INDEX)); | |
| cmdList->SetGraphicsRootDescriptorTable( | |
| SAMPLER_SIG_INDEX, | |
| m_samplerHeap->gpuHandleForIndex(0)); | |
| cmdList->SetGraphicsRootDescriptorTable( | |
| DYNAMIC_SAMPLER_SIG_INDEX, | |
| m_samplerHeap->gpuHandleForIndex(IMAGE_SAMPLER_HEAP_OFFSET)); | |
| } | |
| // note that flush could have been left a heap but it would be pointless | |
| // since it would have been a heap size of 1, so instead we bind to as a | |
| // root constant buffer view instead | |
| cmdList->SetGraphicsRootConstantBufferView( | |
| FLUSH_UNIFORM_BUFFFER_SIG_INDEX, | |
| m_flushUniformBuffer->resource()->getGPUVirtualAddress() + | |
| desc.flushUniformDataOffsetInBytes); | |
| // Bind the image-draw-attribute records as a per-instance vertex buffer | |
| // once; each image draw selects its record via the draw call's base | |
| // instance (batch.baseElement). | |
| // NOTE: Even if there are no image draws, we have still allocated a | |
| // single-element instance buffer. | |
| D3D12_VERTEX_BUFFER_VIEW imageDrawInstanceVBV = | |
| m_imageDrawInstanceBuffer->resource()->vertexBufferView( | |
| sizeof(gpu::ImageDrawInstance)); | |
| cmdList->IASetVertexBuffers(IMAGE_DRAW_INSTANCE_DATA_SLOT, | |
| 1, | |
| &imageDrawInstanceVBV); | |
| if (desc.pathCount > 0) | |
| { | |
| m_cpuSrvUavCbvHeap->markSrvToIndex( | |
| m_device.Get(), | |
| m_pathBuffer->resource(), | |
| PATH_BUFFER_HEAP_OFFSET, | |
| desc.pathCount, | |
| sizeof(PathData), | |
| StorageBufferElementSizeInBytes(PathData::kBufferStructure), | |
| desc.firstPath); | |
| m_cpuSrvUavCbvHeap->markSrvToIndex( | |
| m_device.Get(), | |
| m_paintBuffer->resource(), | |
| PAINT_BUFFER_HEAP_OFFSET, | |
| desc.pathCount, | |
| sizeof(PaintData), | |
| StorageBufferElementSizeInBytes(PaintData::kBufferStructure), | |
| desc.firstPaint); | |
| m_cpuSrvUavCbvHeap->markSrvToIndex( | |
| m_device.Get(), | |
| m_paintAuxBuffer->resource(), | |
| PAINT_AUX_BUFFER_HEAP_OFFSET, | |
| desc.pathCount, | |
| sizeof(PaintAuxData), | |
| StorageBufferElementSizeInBytes(PaintAuxData::kBufferStructure), | |
| desc.firstPaintAux); | |
| } | |
| else | |
| { | |
| // Same reason as the texture-SRV null-fallback above — recycled cpu | |
| // heap slots may retain stale buffer descriptors. | |
| m_cpuSrvUavCbvHeap->markNullStructuredBufferSrvToIndex( | |
| m_device.Get(), | |
| PATH_BUFFER_HEAP_OFFSET, | |
| StorageBufferElementSizeInBytes(PathData::kBufferStructure)); | |
| m_cpuSrvUavCbvHeap->markNullStructuredBufferSrvToIndex( | |
| m_device.Get(), | |
| PAINT_BUFFER_HEAP_OFFSET, | |
| StorageBufferElementSizeInBytes(PaintData::kBufferStructure)); | |
| m_cpuSrvUavCbvHeap->markNullStructuredBufferSrvToIndex( | |
| m_device.Get(), | |
| PAINT_AUX_BUFFER_HEAP_OFFSET, | |
| StorageBufferElementSizeInBytes(PaintAuxData::kBufferStructure)); | |
| } | |
| if (desc.contourCount > 0) | |
| { | |
| m_cpuSrvUavCbvHeap->markSrvToIndex( | |
| m_device.Get(), | |
| m_contourBuffer->resource(), | |
| CONTOUR_BUFFER_HEAP_OFFSET, | |
| desc.contourCount, | |
| sizeof(ContourData), | |
| StorageBufferElementSizeInBytes(ContourData::kBufferStructure), | |
| desc.firstContour); | |
| } | |
| else | |
| { | |
| m_cpuSrvUavCbvHeap->markNullStructuredBufferSrvToIndex( | |
| m_device.Get(), | |
| CONTOUR_BUFFER_HEAP_OFFSET, | |
| StorageBufferElementSizeInBytes(ContourData::kBufferStructure)); | |
| } | |
| // copy to gpu heap | |
| const UINT dynamicSrvHeapIndex = | |
| m_heapDescriptorOffset + DYNAMIC_SRV_UAV_HEAP_DESCRIPTOPR_START; | |
| m_device->CopyDescriptorsSimple( | |
| NUM_DYNAMIC_SRV_HEAP_DESCRIPTORS, | |
| m_srvUavCbvHeap->cpuHandleForUpload(dynamicSrvHeapIndex), | |
| m_cpuSrvUavCbvHeap->cpuHandleForIndex( | |
| DYNAMIC_SRV_UAV_HEAP_DESCRIPTOPR_START), | |
| D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); | |
| cmdList->SetGraphicsRootDescriptorTable( | |
| DYNAMIC_SRV_SIG_INDEX, | |
| m_srvUavCbvHeap->gpuHandleForIndex(dynamicSrvHeapIndex)); | |
| if (desc.gradSpanCount) | |
| { | |
| CD3DX12_VIEWPORT viewport(0.0f, | |
| 0.0f, | |
| static_cast<float>(kGradTextureWidth), | |
| static_cast<float>(desc.gradDataHeight)); | |
| CD3DX12_RECT scissorRect(0, | |
| 0, | |
| static_cast<LONG>(kGradTextureWidth), | |
| static_cast<LONG>(desc.gradDataHeight)); | |
| m_resourceManager->transition(cmdList, | |
| m_gradientTexture.get(), | |
| D3D12_RESOURCE_STATE_RENDER_TARGET); | |
| auto rtvHandle = m_rtvHeap->cpuHandleForIndex(GRAD_RTV_HEAP_OFFSET); | |
| cmdList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); | |
| cmdList->RSSetViewports(1, &viewport); | |
| cmdList->RSSetScissorRects(1, &scissorRect); | |
| auto view = m_gradSpanBuffer->resource()->vertexBufferView( | |
| 0, | |
| sizeof(GradientSpan)); | |
| cmdList->IASetVertexBuffers(0, 1, &view); | |
| m_pipelineManager.setGradientPipeline(cmdList); | |
| cmdList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); | |
| cmdList->SetGraphicsRoot32BitConstant( | |
| VERTEX_DRAW_UNIFORM_SIG_INDEX, | |
| math::lossless_numeric_cast<UINT>(desc.firstGradSpan), | |
| 0); | |
| cmdList->DrawInstanced( | |
| gpu::GRAD_SPAN_TRI_STRIP_VERTEX_COUNT, | |
| desc.gradSpanCount, | |
| 0, | |
| math::lossless_numeric_cast<UINT>(desc.firstGradSpan)); | |
| m_resourceManager->transition(cmdList, | |
| m_gradientTexture.get(), | |
| D3D12_RESOURCE_STATE_GENERIC_READ); | |
| cmdList->OMSetRenderTargets(0, nullptr, FALSE, nullptr); | |
| } | |
| if (desc.tessVertexSpanCount) | |
| { | |
| RIVE_PROF_GPUNAME_L(1, "Tessellate Curves"); | |
| m_resourceManager->transition(cmdList, | |
| m_tesselationTexture.get(), | |
| D3D12_RESOURCE_STATE_RENDER_TARGET); | |
| auto rtvHandle = m_rtvHeap->cpuHandleForIndex(TESS_RTV_HEAP_OFFSET); | |
| cmdList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); | |
| CD3DX12_VIEWPORT viewport(0.0f, | |
| 0.0f, | |
| static_cast<float>(kTessTextureWidth), | |
| static_cast<float>(desc.tessDataHeight)); | |
| CD3DX12_RECT scissorRect(0, | |
| 0, | |
| static_cast<LONG>(kTessTextureWidth), | |
| static_cast<LONG>(desc.tessDataHeight)); | |
| cmdList->RSSetViewports(1, &viewport); | |
| cmdList->RSSetScissorRects(1, &scissorRect); | |
| cmdList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); | |
| auto VBView = m_tessSpanBuffer->resource()->vertexBufferView( | |
| sizeof(TessVertexSpan)); | |
| auto IBView = | |
| m_tessSpanIndexBuffer->indexBufferView(0, sizeof(kTessSpanIndices)); | |
| cmdList->IASetVertexBuffers(0, 1, &VBView); | |
| cmdList->IASetIndexBuffer(&IBView); | |
| m_pipelineManager.setTesselationPipeline(cmdList); | |
| cmdList->DrawIndexedInstanced( | |
| std::size(gpu::kTessSpanIndices), | |
| desc.tessVertexSpanCount, | |
| 0, | |
| 0, | |
| math::lossless_numeric_cast<UINT>(desc.firstTessVertexSpan)); | |
| cmdList->OMSetRenderTargets(0, nullptr, FALSE, nullptr); | |
| m_resourceManager->transition(cmdList, | |
| m_tesselationTexture.get(), | |
| D3D12_RESOURCE_STATE_GENERIC_READ); | |
| if (m_capabilities.isIntel) | |
| { | |
| // workaround similiar to d3d11. | |
| // Intel needs this which is basically a | |
| // "VK_DEPENDENCY_BY_REGION_BIT" pipeline barrier | |
| const auto barrier = | |
| CD3DX12_RESOURCE_BARRIER::Aliasing(nullptr, nullptr); | |
| cmdList->ResourceBarrier(1, &barrier); | |
| } | |
| } | |
| D3D12_VERTEX_BUFFER_VIEW NULL_VIEW; | |
| NULL_VIEW.BufferLocation = 0; | |
| NULL_VIEW.SizeInBytes = 0; | |
| NULL_VIEW.StrideInBytes = 0; | |
| D3D12_VERTEX_BUFFER_VIEW vertexBuffers[3] = { | |
| m_pathPatchVertexBuffer->vertexBufferView(0, | |
| kPatchVertexBufferCount, | |
| sizeof(PatchVertex)), | |
| desc.hasTriangleVertices | |
| ? m_triangleBuffer->resource()->vertexBufferView( | |
| 0, | |
| sizeof(TriangleVertex)) | |
| : NULL_VIEW, | |
| m_imageRectVertexBuffer->vertexBufferView(0, | |
| std::size(kImageRectVertices), | |
| sizeof(ImageRectVertex))}; | |
| static_assert(PATCH_VERTEX_DATA_SLOT == 0); | |
| static_assert(TRIANGLE_VERTEX_DATA_SLOT == 1); | |
| static_assert(IMAGE_RECT_VERTEX_DATA_SLOT == 2); | |
| cmdList->IASetVertexBuffers(0, 3, vertexBuffers); | |
| if ((desc.featherAtlasFillBatchCount | desc.featherAtlasStrokeBatchCount) != | |
| 0) | |
| { | |
| RIVE_PROF_GPUNAME_L(1, "featherAtlasRender"); | |
| m_resourceManager->transition(cmdList, | |
| m_featherAtlasTexture.get(), | |
| D3D12_RESOURCE_STATE_RENDER_TARGET); | |
| auto rtvHandle = | |
| m_rtvHeap->cpuHandleForIndex(FEATHER_ATLAS_RTV_HEAP_OFFSET); | |
| cmdList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); | |
| float clearZero[4]{}; | |
| cmdList->ClearRenderTargetView(rtvHandle, clearZero, 0, nullptr); | |
| CD3DX12_VIEWPORT viewport( | |
| 0.0f, | |
| 0.0f, | |
| static_cast<float>(desc.featherAtlasContentWidth), | |
| static_cast<float>(desc.featherAtlasContentHeight)); | |
| cmdList->RSSetViewports(1, &viewport); | |
| cmdList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); | |
| auto IBV = m_pathPatchIndexBuffer->indexBufferView(); | |
| cmdList->IASetIndexBuffer(&IBV); | |
| if (desc.featherAtlasFillBatchCount != 0) | |
| { | |
| m_pipelineManager.setFeatherAtlasFillPipeline(cmdList); | |
| for (size_t i = 0; i < desc.featherAtlasFillBatchCount; ++i) | |
| { | |
| const gpu::AtlasDrawBatch& fillBatch = | |
| desc.featherAtlasFillBatches[i]; | |
| CD3DX12_RECT scissorRect(fillBatch.scissor.left, | |
| fillBatch.scissor.top, | |
| fillBatch.scissor.right, | |
| fillBatch.scissor.bottom); | |
| cmdList->RSSetScissorRects(1, &scissorRect); | |
| cmdList->SetGraphicsRoot32BitConstant( | |
| VERTEX_DRAW_UNIFORM_SIG_INDEX, | |
| fillBatch.basePatch, | |
| 0); | |
| cmdList->DrawIndexedInstanced( | |
| kMidpointFanCenterAAPatchIndexCount, | |
| fillBatch.patchCount, | |
| kMidpointFanCenterAAPatchBaseIndex, | |
| 0, | |
| fillBatch.basePatch); | |
| } | |
| } | |
| if (desc.featherAtlasStrokeBatchCount != 0) | |
| { | |
| m_pipelineManager.setFeatherAtlasStrokePipeline(cmdList); | |
| for (size_t i = 0; i < desc.featherAtlasStrokeBatchCount; ++i) | |
| { | |
| const gpu::AtlasDrawBatch& strokeBatch = | |
| desc.featherAtlasStrokeBatches[i]; | |
| CD3DX12_RECT scissorRect(strokeBatch.scissor.left, | |
| strokeBatch.scissor.top, | |
| strokeBatch.scissor.right, | |
| strokeBatch.scissor.bottom); | |
| cmdList->RSSetScissorRects(1, &scissorRect); | |
| cmdList->SetGraphicsRoot32BitConstant( | |
| VERTEX_DRAW_UNIFORM_SIG_INDEX, | |
| strokeBatch.basePatch, | |
| 0); | |
| cmdList->DrawIndexedInstanced( | |
| gpu::kMidpointFanPatchBorderIndexCount, | |
| strokeBatch.patchCount, | |
| gpu::kMidpointFanPatchBaseIndex, | |
| 0, | |
| strokeBatch.basePatch); | |
| } | |
| } | |
| m_resourceManager->transition(cmdList, | |
| m_featherAtlasTexture.get(), | |
| D3D12_RESOURCE_STATE_GENERIC_READ); | |
| cmdList->OMSetRenderTargets(0, nullptr, FALSE, nullptr); | |
| } | |
| CD3DX12_VIEWPORT viewport(0.0f, | |
| 0.0f, | |
| static_cast<float>(width), | |
| static_cast<float>(height)); | |
| cmdList->RSSetViewports(1, &viewport); | |
| // Setup and clear the PLS textures. | |
| { | |
| RIVE_PROF_GPUNAME_L(1, "clearPLSTextures"); | |
| if (desc.fixedFunctionColorOutput) | |
| { | |
| m_resourceManager->transition(cmdList, | |
| targetTexture, | |
| D3D12_RESOURCE_STATE_RENDER_TARGET); | |
| auto rtvHandle = | |
| m_rtvHeap->cpuHandleForIndex(TARGET_RTV_HEAP_OFFSET); | |
| cmdList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); | |
| if (desc.colorLoadAction == gpu::LoadAction::clear) | |
| { | |
| float clearColor4f[4]; | |
| UnpackColorToRGBA32FPremul(desc.colorClearValue, clearColor4f); | |
| cmdList->ClearRenderTargetView(rtvHandle, | |
| clearColor4f, | |
| 0, | |
| nullptr); | |
| } | |
| } | |
| else // !desc.fixedFunctionColorOutput | |
| { | |
| if (renderTarget->targetTextureSupportsUAV()) | |
| { | |
| m_resourceManager->transition( | |
| cmdList, | |
| targetTexture, | |
| D3D12_RESOURCE_STATE_UNORDERED_ACCESS); | |
| } | |
| if (desc.colorLoadAction == gpu::LoadAction::clear) | |
| { | |
| auto tex = renderTarget->targetTextureSupportsUAV() | |
| ? renderTarget->targetTexture()->resource() | |
| : renderTarget->offscreenTexture()->resource(); | |
| if (m_capabilities.supportsTypedUAVLoadStore) | |
| { | |
| float clearColor4f[4]; | |
| UnpackColorToRGBA32FPremul(desc.colorClearValue, | |
| clearColor4f); | |
| auto gpuHandle = m_srvUavCbvHeap->gpuHandleForIndex( | |
| ATOMIC_COLOR_HEAP_OFFSET); | |
| auto cpuHandle = m_cpuSrvUavCbvHeap->cpuHandleForIndex( | |
| ATOMIC_COLOR_HEAP_OFFSET); | |
| m_resourceManager->clearUAV(cmdList, | |
| tex, | |
| gpuHandle, | |
| cpuHandle, | |
| clearColor4f, | |
| desc.interlockMode == | |
| InterlockMode::atomics); | |
| } | |
| else | |
| { | |
| UINT clearColorui[4] = {gpu::SwizzleRiveColorToRGBAPremul( | |
| desc.colorClearValue)}; | |
| auto gpuHandle = m_srvUavCbvHeap->gpuHandleForIndex( | |
| ATOMIC_COLOR_HEAP_OFFSET); | |
| auto cpuHandle = m_cpuSrvUavCbvHeap->cpuHandleForIndex( | |
| ATOMIC_COLOR_HEAP_OFFSET); | |
| m_resourceManager->clearUAV(cmdList, | |
| tex, | |
| gpuHandle, | |
| cpuHandle, | |
| clearColorui, | |
| desc.interlockMode == | |
| InterlockMode::atomics); | |
| } | |
| } | |
| if (desc.colorLoadAction == gpu::LoadAction::preserveRenderTarget && | |
| !renderTarget->targetTextureSupportsUAV()) | |
| { | |
| auto offscreenTex = renderTarget->offscreenTexture(); | |
| blitSubRect(cmdList, | |
| offscreenTex, | |
| renderTarget->targetTexture(), | |
| desc.renderTargetUpdateBounds); | |
| m_resourceManager->transition( | |
| cmdList, | |
| offscreenTex, | |
| D3D12_RESOURCE_STATE_UNORDERED_ACCESS); | |
| } | |
| } | |
| } | |
| bool renderPassHasCoalescedResolveAndTransfer = | |
| desc.interlockMode == gpu::InterlockMode::atomics && | |
| !desc.fixedFunctionColorOutput && | |
| !renderTarget->targetTextureSupportsUAV(); | |
| if (enums::is_flag_set(desc.combinedShaderFeatures, | |
| gpu::ShaderFeatures::ENABLE_CLIPPING)) | |
| { | |
| constexpr static UINT kZero[4]{}; | |
| auto gpuHandle = | |
| m_srvUavCbvHeap->gpuHandleForIndex(ATOMIC_CLIP_HEAP_OFFSET); | |
| auto cpuHandle = | |
| m_cpuSrvUavCbvHeap->cpuHandleForIndex(ATOMIC_CLIP_HEAP_OFFSET); | |
| m_resourceManager->clearUAV(cmdList, | |
| renderTarget->clip()->resource(), | |
| gpuHandle, | |
| cpuHandle, | |
| kZero, | |
| desc.interlockMode == | |
| InterlockMode::atomics); | |
| } | |
| // always clear coverage | |
| { | |
| UINT coverageClear[4]{desc.coverageClearValue}; | |
| auto gpuHandle = | |
| m_srvUavCbvHeap->gpuHandleForIndex(ATOMIC_COVERAGE_HEAP_OFFSET); | |
| auto cpuHandle = | |
| m_cpuSrvUavCbvHeap->cpuHandleForIndex(ATOMIC_COVERAGE_HEAP_OFFSET); | |
| m_resourceManager->clearUAV(cmdList, | |
| renderTarget->coverage()->resource(), | |
| gpuHandle, | |
| cpuHandle, | |
| coverageClear, | |
| desc.interlockMode == | |
| InterlockMode::atomics); | |
| } | |
| if (renderPassHasCoalescedResolveAndTransfer) | |
| { | |
| m_resourceManager->transition(cmdList, | |
| targetTexture, | |
| D3D12_RESOURCE_STATE_RENDER_TARGET); | |
| auto rtvHandle = m_rtvHeap->cpuHandleForIndex(TARGET_RTV_HEAP_OFFSET); | |
| cmdList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); | |
| } | |
| m_heapDescriptorOffset += imageDescriptorOffset; | |
| const auto fullUpdateScissorRect = | |
| desc.renderTargetUpdateBounds.lossless_numeric_cast<uint16_t>(); | |
| // Start the current scissor rect out inside-out to guarantee that the first | |
| // rectangle we get doesn't match it. | |
| auto currentScissorRect = AABBu16{0xffff, 0xffff, 0, 0}; | |
| RIVE_PROF_GPUNAME_L(1, "DrawList"); | |
| for (const DrawBatch& batch : *desc.drawList) | |
| { | |
| assert(batch.elementCount != 0); | |
| DrawType drawType = batch.drawType; | |
| auto shaderFeatures = desc.interlockMode == gpu::InterlockMode::atomics | |
| ? desc.combinedShaderFeatures | |
| : batch.shaderFeatures; | |
| auto shaderMiscFlags = batch.shaderMiscFlags; | |
| if (drawType == gpu::DrawType::renderPassResolve && | |
| renderPassHasCoalescedResolveAndTransfer) | |
| { | |
| assert(desc.interlockMode == gpu::InterlockMode::atomics); | |
| shaderMiscFlags |= | |
| gpu::ShaderMiscFlags::coalescedResolveAndTransfer; | |
| } | |
| auto* pipeline = m_pipelineManager.tryGetPipeline( | |
| { | |
| .drawType = drawType, | |
| .shaderFeatures = shaderFeatures, | |
| .interlockMode = desc.interlockMode, | |
| .shaderMiscFlags = shaderMiscFlags, | |
| .wireframe = desc.wireframe, | |
| #ifdef WITH_RIVE_TOOLS | |
| .synthesizedFailureType = desc.synthesizedFailureType, | |
| #endif | |
| }, | |
| m_platformFeatures); | |
| // all atomic barriers are the same for dx12 | |
| if (enums::any_flag_set(batch.barriers, | |
| BarrierFlags::plsAtomicPreResolve | | |
| BarrierFlags::plsAtomic)) | |
| { | |
| assert(desc.interlockMode == gpu::InterlockMode::atomics); | |
| auto target = renderTarget->targetTextureSupportsUAV() | |
| ? renderTarget->targetTexture() | |
| : renderTarget->offscreenTexture(); | |
| D3D12_RESOURCE_BARRIER barriers[] = { | |
| CD3DX12_RESOURCE_BARRIER::UAV( | |
| renderTarget->coverage()->resource()), | |
| CD3DX12_RESOURCE_BARRIER::UAV(target->resource()), | |
| CD3DX12_RESOURCE_BARRIER::UAV( | |
| renderTarget->clip()->resource())}; | |
| cmdList->ResourceBarrier( | |
| enums::is_flag_set(desc.combinedShaderFeatures, | |
| gpu::ShaderFeatures::ENABLE_CLIPPING) | |
| ? 3 | |
| : 2, | |
| barriers); | |
| } | |
| if (pipeline == nullptr) | |
| { | |
| // There was an issue getting either the requested pipeline state or | |
| // its ubershader counterpart so we cannot draw anything. | |
| continue; | |
| } | |
| { | |
| auto desiredScissorRect = | |
| batch.scissorRect.has_value() | |
| ? fullUpdateScissorRect.intersectOrEmpty( | |
| batch.scissorRect.value()) | |
| : fullUpdateScissorRect; | |
| if (desiredScissorRect != currentScissorRect) | |
| { | |
| currentScissorRect = desiredScissorRect; | |
| CD3DX12_RECT scissorRect{currentScissorRect.left, | |
| currentScissorRect.top, | |
| currentScissorRect.right, | |
| currentScissorRect.bottom}; | |
| cmdList->RSSetScissorRects(1, &scissorRect); | |
| } | |
| } | |
| cmdList->SetPipelineState(pipeline->m_d3dPipelineState.Get()); | |
| if (auto imageTextureD3D12 = | |
| static_cast<const TextureD3D12Impl*>(batch.imageTexture)) | |
| { | |
| auto imageTexture = | |
| imageTextureD3D12->synchronize(copyCmdList, | |
| cmdList, | |
| m_resourceManager.get()); | |
| RNAME_D3D12_OBJECT(imageTexture, m_heapDescriptorOffset); | |
| if (m_heapDescriptorOffset >= MAX_DESCRIPTOR_HEAPS_PER_FLUSH) | |
| { | |
| fprintf(stderr, | |
| "heap descriptor ran out of room ! heapOffset %i " | |
| "maxHeap %i\n", | |
| m_heapDescriptorOffset, | |
| MAX_DESCRIPTOR_HEAPS_PER_FLUSH); | |
| assert(false); | |
| // break out of loop and let transitions happen | |
| break; | |
| } | |
| m_srvUavCbvHeap->markSrvToIndex(m_device.Get(), | |
| imageTexture, | |
| m_heapDescriptorOffset); | |
| cmdList->SetGraphicsRootDescriptorTable( | |
| IMAGE_SIG_INDEX, | |
| m_srvUavCbvHeap->gpuHandleForIndex(m_heapDescriptorOffset)); | |
| ++m_heapDescriptorOffset; | |
| // we want to update this as little as possible, so only set it if | |
| // it's changed | |
| if (m_lastDynamicSampler != batch.imageSampler) | |
| { | |
| if (++m_samplerHeapDescriptorOffset >= | |
| MAX_DESCRIPTOR_SAMPLER_HEAPS_PER_FLUSH) | |
| { | |
| m_samplerHeap = m_samplerHeapPool.acquire(); | |
| m_samplerHeapDescriptorOffset = IMAGE_SAMPLER_HEAP_OFFSET; | |
| // Re-create the immutable samplers in the new heap. | |
| // CopyDescriptorsSimple from the old heap is invalid: | |
| // shader-visible heaps are CPU-write-only per D3D12 | |
| // spec, and reading from one as a copy source produces | |
| // garbage descriptors (validation fires id=654, GPU | |
| // eventually TDRs). Mirror the initial-flush block that | |
| // creates these from m_linearSampler directly. | |
| m_samplerHeap->markSamplerToIndex(m_device.Get(), | |
| m_linearSampler, | |
| TESS_SAMPLER_HEAP_OFFSET); | |
| m_samplerHeap->markSamplerToIndex(m_device.Get(), | |
| m_linearSampler, | |
| GRAD_SAMPLER_HEAP_OFFSET); | |
| m_samplerHeap->markSamplerToIndex( | |
| m_device.Get(), | |
| m_linearSampler, | |
| GAUSSIAN_INTEGRAL_SAMPLER_HEAP_OFFSET); | |
| m_samplerHeap->markSamplerToIndex( | |
| m_device.Get(), | |
| m_linearSampler, | |
| FEATHER_ATLAS_SAMPLER_HEAP_OFFSET); | |
| ID3D12DescriptorHeap* ppHeaps[] = {m_srvUavCbvHeap->heap(), | |
| m_samplerHeap->heap()}; | |
| cmdList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps); | |
| // SetDescriptorHeaps invalidates every previously-bound | |
| // root descriptor table. Re-bind every table that was | |
| // set up earlier in this flush, otherwise GPU-Based | |
| // Validation fires id=554 on every subsequent draw and | |
| // the runtime eventually TDRs the device. | |
| cmdList->SetGraphicsRootDescriptorTable( | |
| STATIC_SRV_SIG_INDEX, | |
| m_srvUavCbvHeap->gpuHandleForIndex( | |
| STATIC_SRV_UAV_HEAP_DESCRIPTOPR_START)); | |
| cmdList->SetGraphicsRootDescriptorTable( | |
| UAV_SIG_INDEX, | |
| m_srvUavCbvHeap->gpuHandleForIndex( | |
| UAV_START_HEAP_INDEX)); | |
| cmdList->SetGraphicsRootDescriptorTable( | |
| SAMPLER_SIG_INDEX, | |
| m_samplerHeap->gpuHandleForIndex(0)); | |
| cmdList->SetGraphicsRootDescriptorTable( | |
| DYNAMIC_SRV_SIG_INDEX, | |
| m_srvUavCbvHeap->gpuHandleForIndex( | |
| dynamicSrvHeapIndex)); | |
| cmdList->SetGraphicsRootDescriptorTable( | |
| IMAGE_SIG_INDEX, | |
| m_srvUavCbvHeap->gpuHandleForIndex( | |
| m_heapDescriptorOffset - 1)); | |
| // DYNAMIC_SAMPLER_SIG_INDEX is rebound by the existing | |
| // call below. | |
| } | |
| m_samplerHeap->markSamplerToIndex( | |
| m_device.Get(), | |
| m_imageSamplers[batch.imageSampler.asKey()], | |
| m_samplerHeapDescriptorOffset); | |
| cmdList->SetGraphicsRootDescriptorTable( | |
| DYNAMIC_SAMPLER_SIG_INDEX, | |
| m_samplerHeap->gpuHandleForIndex( | |
| m_samplerHeapDescriptorOffset)); | |
| m_lastDynamicSampler = batch.imageSampler; | |
| } | |
| } | |
| switch (drawType) | |
| { | |
| case DrawType::midpointFanPatches: | |
| case DrawType::midpointFanCenterAAPatches: | |
| case DrawType::outerCurvePatches: | |
| { | |
| RIVE_PROF_GPUNAME_L(2, "Patches"); | |
| cmdList->IASetPrimitiveTopology( | |
| D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); | |
| auto IBV = m_pathPatchIndexBuffer->indexBufferView(); | |
| cmdList->IASetIndexBuffer(&IBV); | |
| cmdList->SetGraphicsRoot32BitConstant( | |
| VERTEX_DRAW_UNIFORM_SIG_INDEX, | |
| batch.baseElement, | |
| 0); | |
| cmdList->DrawIndexedInstanced(batch.indexCountPerInstance, | |
| batch.elementCount, | |
| batch.baseIndex, | |
| 0, | |
| batch.baseElement); | |
| break; | |
| } | |
| case DrawType::interiorTriangulation: | |
| case DrawType::featherAtlasBlit: | |
| { | |
| RIVE_PROF_GPUNAME_L(2, | |
| "interiorTriangulation||featherAtlasBlit"); | |
| cmdList->IASetPrimitiveTopology( | |
| D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); | |
| cmdList->DrawInstanced(batch.elementCount, | |
| 1, | |
| batch.baseElement, | |
| 0); | |
| break; | |
| } | |
| case DrawType::imageRect: | |
| { | |
| RIVE_PROF_GPUNAME_L(2, "imageRect"); | |
| cmdList->IASetPrimitiveTopology( | |
| D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); | |
| auto IBV = m_imageRectIndexBuffer->indexBufferView(); | |
| cmdList->IASetIndexBuffer(&IBV); | |
| cmdList->DrawIndexedInstanced(batch.indexCountPerInstance, | |
| batch.elementCount, | |
| batch.baseIndex, | |
| 0, | |
| batch.baseElement); | |
| break; | |
| } | |
| case DrawType::imageMesh: | |
| { | |
| RIVE_PROF_GPUNAME_L(2, "imageMesh"); | |
| LITE_RTTI_CAST_OR_BREAK(vertexBuffer, | |
| RenderBufferD3D12Impl*, | |
| batch.vertexBuffer); | |
| LITE_RTTI_CAST_OR_BREAK(uvBuffer, | |
| RenderBufferD3D12Impl*, | |
| batch.uvBuffer); | |
| LITE_RTTI_CAST_OR_BREAK(indexBuffer, | |
| RenderBufferD3D12Impl*, | |
| batch.indexBuffer); | |
| auto vBuffer = vertexBuffer->sync(copyCmdList); | |
| auto uBuffer = uvBuffer->sync(copyCmdList); | |
| auto iBuffer = indexBuffer->sync(copyCmdList); | |
| D3D12_VERTEX_BUFFER_VIEW imageMeshBuffers[] = { | |
| vBuffer->vertexBufferView(0, sizeof(Vec2D)), | |
| uBuffer->vertexBufferView(0, sizeof(Vec2D))}; | |
| cmdList->IASetVertexBuffers(IMAGE_MESH_VERTEX_DATA_SLOT, | |
| 2, | |
| imageMeshBuffers); | |
| static_assert(IMAGE_MESH_UV_DATA_SLOT == | |
| IMAGE_MESH_VERTEX_DATA_SLOT + 1); | |
| cmdList->IASetPrimitiveTopology( | |
| D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); | |
| auto IBV = iBuffer->indexBufferView(); | |
| cmdList->IASetIndexBuffer(&IBV); | |
| cmdList->DrawIndexedInstanced(batch.indexCountPerInstance, | |
| batch.elementCount, | |
| batch.baseIndex, | |
| 0, | |
| batch.baseElement); | |
| break; | |
| } | |
| case DrawType::renderPassResolve: | |
| { | |
| RIVE_PROF_GPUNAME_L(1, "renderPassResolve"); | |
| assert(desc.interlockMode == gpu::InterlockMode::atomics); | |
| cmdList->IASetPrimitiveTopology( | |
| D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); | |
| cmdList->DrawInstanced(4, 1, 0, 0); | |
| break; | |
| } | |
| case DrawType::depthStrokes: | |
| case DrawType::stencilMidpointFanBorrowedCoverage: | |
| case DrawType::stencilDynamicMidpointFans: | |
| case DrawType::stencilDynamicOuterCubics: | |
| case DrawType::stencilMidpointFans: | |
| case DrawType::stencilMidpointFanReset: | |
| case DrawType::stencilMidpointFanWinding: | |
| case DrawType::stencilMidpointFanCover: | |
| case DrawType::stencilOuterCubicBorrowedCoverage: | |
| case DrawType::stencilOuterCubicReset: | |
| case DrawType::stencilOuterCubicWinding: | |
| case DrawType::stencilOuterCubicCover: | |
| case DrawType::stencilOuterCubics: | |
| case DrawType::clipReset: | |
| case DrawType::renderPassInitialize: | |
| RIVE_UNREACHABLE(); | |
| } | |
| } | |
| if (desc.interlockMode == gpu::InterlockMode::rasterOrdering && | |
| !renderTarget->targetTextureSupportsUAV()) | |
| { | |
| RIVE_PROF_GPUNAME_L(1, "blit_sub_rect"); | |
| // We rendered to an offscreen UAV and did not resolve to the | |
| // renderTarget. Copy back to the main target. | |
| assert(!desc.fixedFunctionColorOutput); | |
| assert(!renderPassHasCoalescedResolveAndTransfer); | |
| blitSubRect(cmdList, | |
| renderTarget->targetTexture(), | |
| renderTarget->offscreenTexture(), | |
| desc.renderTargetUpdateBounds); | |
| m_resourceManager->transition(cmdList, | |
| targetTexture, | |
| D3D12_RESOURCE_STATE_COMMON); | |
| // we create the offscreen texture in UAV state and always assume it is | |
| // in it. so put it back | |
| m_resourceManager->transition(cmdList, | |
| renderTarget->offscreenTexture(), | |
| D3D12_RESOURCE_STATE_UNORDERED_ACCESS); | |
| } | |
| if (desc.fixedFunctionColorOutput || | |
| renderPassHasCoalescedResolveAndTransfer || | |
| (renderTarget->targetTextureSupportsUAV() && | |
| !desc.fixedFunctionColorOutput)) | |
| { | |
| m_resourceManager->transition(cmdList, | |
| targetTexture, | |
| D3D12_RESOURCE_STATE_COMMON); | |
| } | |
| } | |
| void RenderContextD3D12Impl::postFlush(const RenderContext::FlushResources&) | |
| { | |
| // Recycle buffers. | |
| m_flushUniformBufferPool.recycle(std::move(m_flushUniformBuffer)); | |
| m_pathBufferPool.recycle(std::move(m_pathBuffer)); | |
| m_paintBufferPool.recycle(std::move(m_paintBuffer)); | |
| m_paintAuxBufferPool.recycle(std::move(m_paintAuxBuffer)); | |
| m_contourBufferPool.recycle(std::move(m_contourBuffer)); | |
| m_gradSpanBufferPool.recycle(std::move(m_gradSpanBuffer)); | |
| m_tessSpanBufferPool.recycle(std::move(m_tessSpanBuffer)); | |
| m_triangleBufferPool.recycle(std::move(m_triangleBuffer)); | |
| m_imageDrawInstanceBufferPool.recycle(std::move(m_imageDrawInstanceBuffer)); | |
| m_srvUavCbvHeapPool.recycle(std::move(m_srvUavCbvHeap)); | |
| m_cpuSrvUavCbvHeapPool.recycle(std::move(m_cpuSrvUavCbvHeap)); | |
| m_samplerHeapPool.recycle(std::move(m_samplerHeap)); | |
| } | |
| }; // namespace rive::gpu |