| /* |
| * Copyright 2022 Rive |
| */ |
| |
| #pragma once |
| |
| #include "rive/pls/pls.hpp" |
| |
| namespace rive::pls |
| { |
| // API-agnostic implementation of an abstract buffer ring. We use rings to ensure the GPU can render |
| // one frame in parallel while the CPU prepares the next frame. |
| // |
| // Calling mapBuffer() maps the next buffer in the ring. |
| // |
| // Calling unmapAndSubmitBuffer() submits the currently-mapped buffer for GPU rendering, in whatever |
| // way that is meaningful for the PLSRenderContext implementation. |
| // |
| // This class is meant to only be used through BufferRing<>. |
| class BufferRingImpl |
| { |
| public: |
| BufferRingImpl(size_t capacity, size_t itemSizeInBytes) : |
| m_capacity(capacity), m_itemSizeInBytes(itemSizeInBytes) |
| {} |
| virtual ~BufferRingImpl() {} |
| |
| size_t capacity() const { return m_capacity; } |
| size_t itemSizeInBytes() const { return m_itemSizeInBytes; } |
| size_t totalSizeInBytes() const { return m_capacity * m_itemSizeInBytes * kBufferRingSize; } |
| |
| // Maps the next buffer in the ring. |
| void* mapBuffer() |
| { |
| assert(!m_mapped); |
| RIVE_DEBUG_CODE(m_mapped = true;) |
| m_submittedBufferIdx = (m_submittedBufferIdx + 1) % kBufferRingSize; |
| return onMapBuffer(m_submittedBufferIdx); |
| } |
| |
| // Submits the currently-mapped buffer for GPU rendering, in whatever way that is meaningful for |
| // the PLSRenderContext implementation. |
| void unmapAndSubmitBuffer(size_t bytesWritten) |
| { |
| assert(m_mapped); |
| RIVE_DEBUG_CODE(m_mapped = false;) |
| onUnmapAndSubmitBuffer(m_submittedBufferIdx, bytesWritten); |
| } |
| |
| protected: |
| int submittedBufferIdx() const |
| { |
| assert(!m_mapped); |
| return m_submittedBufferIdx; |
| } |
| |
| virtual void* onMapBuffer(int bufferIdx) = 0; |
| virtual void onUnmapAndSubmitBuffer(int bufferIdx, size_t bytesWritten) = 0; |
| |
| private: |
| size_t m_capacity; |
| size_t m_itemSizeInBytes; |
| int m_submittedBufferIdx = 0; |
| RIVE_DEBUG_CODE(bool m_mapped = false;) |
| }; |
| |
| // Buffer ring implementation for a GPU resource that doesn't support mapping. Mapping is emulated |
| // via shadow buffer on the CPU. |
| class BufferRingShadowImpl : public BufferRingImpl |
| { |
| public: |
| BufferRingShadowImpl(size_t capacity, size_t itemSizeInBytes) : |
| BufferRingImpl(capacity, itemSizeInBytes) |
| {} |
| ~BufferRingShadowImpl() override { free(m_shadowBuffer); } |
| |
| void* onMapBuffer(int bufferIdx) final |
| { |
| if (!m_shadowBuffer) |
| { |
| m_shadowBuffer = malloc(capacity() * itemSizeInBytes()); |
| } |
| return m_shadowBuffer; |
| } |
| |
| const void* shadowBuffer() const { return m_shadowBuffer; } |
| |
| private: |
| void* m_shadowBuffer = nullptr; |
| }; |
| |
| // BufferRingShadowImpl whose backing store is a 2D texture. |
| class TexelBufferRing : public BufferRingShadowImpl |
| { |
| public: |
| enum class Format |
| { |
| rgba8, |
| rgba32f, |
| rgba32ui, |
| }; |
| |
| constexpr static size_t BytesPerPixel(Format format) |
| { |
| switch (format) |
| { |
| case Format::rgba8: |
| return 4; |
| case Format::rgba32f: |
| case Format::rgba32ui: |
| return 4 * 4; |
| } |
| RIVE_UNREACHABLE(); |
| } |
| |
| enum class Filter |
| { |
| nearest, |
| linear, |
| }; |
| |
| TexelBufferRing(Format format, size_t widthInItems, size_t height, size_t texelsPerItem) : |
| BufferRingShadowImpl(height * widthInItems, texelsPerItem * BytesPerPixel(format)), |
| m_format(format), |
| m_widthInItems(widthInItems), |
| m_height(height), |
| m_texelsPerItem(texelsPerItem) |
| {} |
| |
| size_t widthInItems() const { return m_widthInItems; } |
| size_t widthInTexels() const { return m_widthInItems * m_texelsPerItem; } |
| size_t height() const { return m_height; } |
| |
| protected: |
| void onUnmapAndSubmitBuffer(int bufferIdx, size_t bytesWritten) final |
| { |
| size_t texelsWritten = bytesWritten / BytesPerPixel(m_format); |
| assert(texelsWritten * BytesPerPixel(m_format) == bytesWritten); |
| size_t updateWidthInTexels = std::min(texelsWritten, widthInTexels()); |
| size_t updateHeight = (texelsWritten + widthInTexels() - 1) / widthInTexels(); |
| submitTexels(bufferIdx, updateWidthInTexels, updateHeight); |
| } |
| |
| virtual void submitTexels(int textureIdx, size_t updateWidthInTexels, size_t updateHeight) = 0; |
| |
| const Format m_format; |
| const size_t m_widthInItems; |
| const size_t m_height; |
| const size_t m_texelsPerItem; |
| }; |
| |
| // Buffer ring implementation for buffers that only exist on the CPU. |
| class CPUOnlyBufferRing : public BufferRingImpl |
| { |
| public: |
| CPUOnlyBufferRing(size_t capacity, size_t itemSizeInBytes) : |
| BufferRingImpl(capacity, itemSizeInBytes) |
| { |
| for (int i = 0; i < kBufferRingSize; ++i) |
| m_cpuBuffers[i].reset(new char[capacity * itemSizeInBytes]); |
| } |
| |
| const void* submittedata() const { return m_cpuBuffers[submittedBufferIdx()].get(); } |
| |
| protected: |
| void* onMapBuffer(int bufferIdx) override { return m_cpuBuffers[bufferIdx].get(); } |
| void onUnmapAndSubmitBuffer(int bufferIdx, size_t bytesWritten) override {} |
| |
| private: |
| std::unique_ptr<char[]> m_cpuBuffers[kBufferRingSize]; |
| }; |
| } // namespace rive::pls |