#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_ #define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_ #include "common/alignment.h" #include "common/assert.h" #include "common/slotVector.h" #include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/renderer/image/imageInfo.h" #include #include #include #include #include #include namespace Libs::Graphics { class Buffer; class CommandScheduler; struct ImageTestAccess; using ImageId = Common::SlotId; struct CachedImageView { ImageViewInfo info; vk::ImageView view = nullptr; }; struct ImageUsage { bool texture = false; bool storage = false; bool render_target = false; bool depth_target = false; bool video_out = false; }; struct ImageBinding { vk::ImageLayout attachment_layout = vk::ImageLayout::eUndefined; vk::AccessFlags2 attachment_access; bool is_bound = false; bool is_target = false; bool needs_rebind = false; bool force_general = false; bool shader_write = false; }; class Image final { public: Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageInfo& info); ~Image(); KYTY_CLASS_NO_COPY(Image); [[nodiscard]] vk::ImageView FindView(const ImageViewInfo& view_info); using Barriers = std::vector; [[nodiscard]] Barriers GetBarriers(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access, vk::PipelineStageFlags2 destination_stage, std::optional range); void Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access, std::optional range, vk::CommandBuffer command_buffer); void Upload(std::span copies, vk::Buffer buffer, uint64_t offset, uint64_t size); void Download(std::span copies, vk::Buffer buffer, uint64_t offset, uint64_t size); void CopyImage(Image& source); void Resolve(Image& source, const ImageSubresourceRange& source_range, const ImageSubresourceRange& destination_range); void CopyImageWithBuffer(Image& source, Buffer& buffer); void CopyMip(Image& source, uint32_t mip, uint32_t layer); void InvalidateCpuWrite(uint64_t vaddr, uint64_t size) { if (ImageRangeOverlaps(info.data.address, info.data.size, vaddr, size)) { m_cpu_dirty = true; m_maybe_cpu_dirty = false; m_maybe_hash_valid = false; } else if (ImagePageRangesOverlap(info.data.address, info.data.size, vaddr, size)) { m_maybe_cpu_dirty = true; } } [[nodiscard]] bool IsCpuDirty() const { return m_cpu_dirty || m_maybe_cpu_dirty; } [[nodiscard]] bool IsDefinitelyCpuDirty() const { return m_cpu_dirty; } [[nodiscard]] bool IsMaybeCpuDirty() const { return m_maybe_cpu_dirty; } void MarkMaybeCpuDirty() { if (!m_cpu_dirty) { m_maybe_cpu_dirty = true; } } [[nodiscard]] bool NeedsMaybeCpuHash() const { return m_maybe_cpu_dirty && !m_maybe_hash_valid; } void SetMaybeCpuHash(uint64_t hash) { if (!NeedsMaybeCpuHash()) { EXIT("image cannot initialize maybe-dirty hash\n"); } m_maybe_cpu_hash = hash; m_maybe_hash_valid = true; } [[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) { if (!m_maybe_cpu_dirty || !m_maybe_hash_valid || m_cpu_dirty) { EXIT("image cannot resolve maybe-dirty hash\n"); } m_maybe_cpu_dirty = false; m_maybe_hash_valid = false; m_cpu_dirty |= hash != m_maybe_cpu_hash; return m_cpu_dirty; } void RefreshComplete() { if (!IsCpuDirty()) { EXIT("clean image cannot complete a refresh\n"); } m_cpu_dirty = false; m_maybe_cpu_dirty = false; m_maybe_hash_valid = false; } [[nodiscard]] bool IsGpuModified() const noexcept { return m_gpu_modified; } void MarkGpuModified() noexcept { m_gpu_modified = true; } void ClearGpuModified() noexcept { m_gpu_modified = false; } [[nodiscard]] bool IsBufferModified() const noexcept { return m_buffer_modified; } void MarkBufferModified() noexcept { m_buffer_modified = true; } void ClearBufferModified() noexcept { m_buffer_modified = false; } [[nodiscard]] bool Overlaps(uint64_t address, uint64_t size, bool pages = false) const noexcept { return pages ? ImagePageRangesOverlap(info.data.address, info.data.size, address, size) : ImageRangeOverlaps(info.data.address, info.data.size, address, size); } [[nodiscard]] bool SafeToDownload() const noexcept { return IsGpuModified() && !IsBufferModified() && !IsCpuDirty(); } [[nodiscard]] bool IsTracked() const noexcept { return track_addr != 0 && track_addr_end != 0; } [[nodiscard]] uint64_t AccountedSize() const noexcept { return backing.image == nullptr ? 0 : Common::AlignUp(info.data.size, 1024); } [[nodiscard]] uint64_t HashGuestEdges() const; ImageInfo info; VulkanImage backing; std::vector views; ImageUsage usage; ImageBinding binding; bool registered = false; mutable uint32_t query_epoch = 0; uint64_t track_addr = 0; uint64_t track_addr_end = 0; ImageId depth_id {}; // The current stencil plane's mapping into the depth image, also retained by its association. ImageSubresourceRange stencil_subresources; uint64_t tick_accessed_last = 0; size_t lru_id = 0; private: friend struct ImageTestAccess; [[nodiscard]] static vk::ImageAspectFlags FullAspectMask(vk::Format format) noexcept; [[nodiscard]] static uint32_t CopyRows(uint64_t row_size, uint32_t rows, uint64_t capacity) noexcept; [[nodiscard]] static std::pair SanitizeCopyLayers(const Image& source, const Image& destination, uint32_t depth); GraphicContext& m_graphics; CommandScheduler& m_scheduler; uint64_t m_maybe_cpu_hash = 0; bool m_cpu_dirty = false; bool m_maybe_cpu_dirty = false; bool m_maybe_hash_valid = false; bool m_gpu_modified = false; bool m_buffer_modified = false; }; namespace ImageOps { void Validate(const ImageInfo& info); [[nodiscard]] Prospero::BufferFormat RenderTargetTransferFormat(uint32_t bytes_per_element); } // namespace ImageOps } // namespace Libs::Graphics #endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_