#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_ #define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_ #include "common/assert.h" #include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/renderer/image/imageInfo.h" #include #include #include #include #include #include #include namespace Libs::Graphics { class Buffer; class CommandScheduler; struct ImageTestAccess; struct ImageId { uint32_t index = std::numeric_limits::max(); uint32_t generation = 0; [[nodiscard]] explicit operator bool() const noexcept { return index != std::numeric_limits::max(); } auto operator<=>(const ImageId&) const = default; }; struct CachedImageView { ImageViewInfo info; vk::ImageView view = nullptr; }; struct ImageViewCache { ImageViewCache() = default; KYTY_CLASS_NO_COPY(ImageViewCache); std::mutex mutex; std::vector views; }; struct ImageUsage { bool texture = false; bool storage = false; bool render_target = false; bool depth_target = false; bool video_out = false; }; struct ImageBinding { bool is_bound = false; bool is_target = false; bool needs_rebind = false; bool force_general = 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); void AssociateDepth(ImageId image_id) { depth_id = image_id; } 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 GpuOverlaps(uint64_t address, uint64_t size) const noexcept { return IsGpuModified() && Overlaps(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 : (info.data.size + 1023) & ~uint64_t {1023}; } [[nodiscard]] uint64_t HashGuestEdges() const; ImageInfo info; VulkanImage backing; ImageViewCache views; ImageUsage usage; ImageBinding binding; bool registered = false; uint64_t track_addr = 0; uint64_t track_addr_end = 0; ImageId depth_id {}; 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 = nullptr; CommandScheduler* m_scheduler = nullptr; 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]] uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element); } // namespace ImageOps } // namespace Libs::Graphics #endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_