video_core: Commonise rasterizer cache
This commit is contained in:
@ -39,6 +39,9 @@ struct Rectangle {
|
||||
return Rectangle{left, top, static_cast<T>(left + GetWidth() * s),
|
||||
static_cast<T>(top + GetHeight() * s)};
|
||||
}
|
||||
[[nodiscard]] Rectangle<T> operator *(const T num) const {
|
||||
return Rectangle{left * num, top * num, right * num, bottom * num};
|
||||
}
|
||||
|
||||
auto operator <=> (const Rectangle<T>& other) const = default;
|
||||
};
|
||||
|
@ -27,9 +27,13 @@ add_library(video_core STATIC
|
||||
common/buffer.h
|
||||
common/framebuffer.h
|
||||
common/pica_types.h
|
||||
common/rasterizer_cache.cpp
|
||||
common/rasterizer_cache.h
|
||||
common/shader_gen.cpp
|
||||
common/shader_gen.h
|
||||
common/shader.h
|
||||
common/surface_params.cpp
|
||||
common/surface_params.h
|
||||
common/texture.h
|
||||
common/pipeline.h
|
||||
renderer_opengl/frame_dumper_opengl.cpp
|
||||
@ -92,6 +96,8 @@ add_library(video_core STATIC
|
||||
renderer_vulkan/vk_format_reinterpreter.h
|
||||
renderer_vulkan/vk_format_util.cpp
|
||||
renderer_vulkan/vk_format_util.h
|
||||
renderer_vulkan/vk_framebuffer.cpp
|
||||
renderer_vulkan/vk_framebuffer.h
|
||||
renderer_vulkan/vk_instance.cpp
|
||||
renderer_vulkan/vk_instance.h
|
||||
renderer_vulkan/vk_pipeline.cpp
|
||||
@ -106,8 +112,6 @@ add_library(video_core STATIC
|
||||
renderer_vulkan/vk_shader_gen.h
|
||||
renderer_vulkan/vk_shader.cpp
|
||||
renderer_vulkan/vk_shader.h
|
||||
renderer_vulkan/vk_state.cpp
|
||||
renderer_vulkan/vk_state.h
|
||||
renderer_vulkan/vk_surface_params.cpp
|
||||
renderer_vulkan/vk_surface_params.h
|
||||
renderer_vulkan/vk_swapchain.cpp
|
||||
|
@ -46,7 +46,7 @@ public:
|
||||
|
||||
// Start an indexed draw operation
|
||||
virtual void DrawIndexed(PipelineHandle pipeline, FramebufferHandle draw_framebuffer,
|
||||
BufferHandle vertex_buffer, BufferHandle index_buffer,
|
||||
BufferHandle vertex_buffer, BufferHandle index_buffer, AttribType index_type,
|
||||
u32 base_index, u32 num_indices, u32 base_vertex) = 0;
|
||||
|
||||
// Executes a compute shader
|
||||
|
@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include "common/assert.h"
|
||||
#include "common/hash.h"
|
||||
#include "common/intrusive_ptr.h"
|
||||
|
||||
@ -45,38 +46,59 @@ static_assert(std::is_standard_layout_v<BufferInfo>, "BufferInfo is not a standa
|
||||
class BufferBase : public IntrusivePtrEnabled<BufferBase> {
|
||||
public:
|
||||
BufferBase() = default;
|
||||
BufferBase(const BufferInfo& info) : info(info) {}
|
||||
BufferBase(const BufferInfo& info) : info(info), bind_range(info.capacity) {}
|
||||
virtual ~BufferBase() = default;
|
||||
|
||||
/// Allocates a linear chunk of memory in the GPU buffer with at least "size" bytes
|
||||
/// and the optional alignment requirement.
|
||||
/// The actual used size must be specified on unmapping the chunk.
|
||||
virtual std::span<u8> Map(u32 size, u32 alignment = 0) {};
|
||||
// Disable copy constructor
|
||||
BufferBase(const BufferBase&) = delete;
|
||||
BufferBase& operator=(const BufferBase&) = delete;
|
||||
|
||||
/// Flushes write to buffer memory
|
||||
virtual void Commit(u32 size = 0) {};
|
||||
// Allocates a linear chunk of memory in the GPU buffer with at least "size" bytes
|
||||
// and the optional alignment requirement.
|
||||
// The actual used size must be specified on unmapping the chunk.
|
||||
virtual std::span<u8> Map(u32 size, u32 alignment = 0) = 0;
|
||||
|
||||
/// Returns the size of the buffer in bytes
|
||||
// Flushes write to buffer memory
|
||||
virtual void Commit(u32 size = 0) = 0;
|
||||
|
||||
// Sets the range of the buffer that will be used when bound
|
||||
void SetBindRange(u32 offset, u32 range) {
|
||||
ASSERT(offset < info.capacity && offset + range < info.capacity);
|
||||
bind_offset = offset;
|
||||
bind_range = range;
|
||||
}
|
||||
|
||||
// Returns the bind offset
|
||||
u32 GetBindOffset() const {
|
||||
return bind_offset;
|
||||
}
|
||||
|
||||
// Returns the number of bytes after bind_offset that will be bound
|
||||
u32 GetBindRange() const {
|
||||
return bind_range;
|
||||
}
|
||||
|
||||
// Returns the size of the buffer in bytes
|
||||
u32 GetCapacity() const {
|
||||
return info.capacity;
|
||||
}
|
||||
|
||||
/// Returns the usage of the buffer
|
||||
// Returns the usage of the buffer
|
||||
BufferUsage GetUsage() const {
|
||||
return info.usage;
|
||||
}
|
||||
|
||||
/// Returns the starting offset of the currently mapped buffer slice
|
||||
// Returns the starting offset of the currently mapped buffer slice
|
||||
u64 GetCurrentOffset() const {
|
||||
return buffer_offset;
|
||||
}
|
||||
|
||||
/// Returns whether the buffer was invalidated by the most recent Map call
|
||||
// Returns whether the buffer was invalidated by the most recent Map call
|
||||
bool IsInvalid() const {
|
||||
return invalid;
|
||||
}
|
||||
|
||||
/// Invalidates the buffer
|
||||
// Invalidates the buffer
|
||||
void Invalidate() {
|
||||
buffer_offset = 0;
|
||||
invalid = true;
|
||||
@ -84,6 +106,8 @@ public:
|
||||
|
||||
protected:
|
||||
BufferInfo info{};
|
||||
u32 bind_offset = 0;
|
||||
u32 bind_range; // Initialized to capacity
|
||||
u32 buffer_offset = 0;
|
||||
bool invalid = false;
|
||||
};
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/common/texture.h"
|
||||
|
||||
namespace VideoCore {
|
||||
@ -22,12 +23,10 @@ struct FramebufferInfo {
|
||||
TextureHandle color;
|
||||
TextureHandle depth_stencil;
|
||||
MSAASamples samples = MSAASamples::x1;
|
||||
Rect2D draw_rect{};
|
||||
|
||||
/// Hashes the framebuffer object and returns a unique identifier
|
||||
// Hashes the framebuffer object and returns a unique identifier
|
||||
const u64 Hash() const {
|
||||
// The only member IntrusivePtr has is a pointer to the
|
||||
// handle so it's fine hash it
|
||||
// IntrusivePtr only has a pointer member so it's fine hash it
|
||||
return Common::ComputeStructHash64(*this);
|
||||
}
|
||||
};
|
||||
@ -40,6 +39,13 @@ public:
|
||||
FramebufferBase(const FramebufferInfo& info) : info(info) {}
|
||||
virtual ~FramebufferBase() = default;
|
||||
|
||||
// Disable copy constructor
|
||||
FramebufferBase(const FramebufferBase&) = delete;
|
||||
FramebufferBase& operator=(const FramebufferBase&) = delete;
|
||||
|
||||
// Clears the attachments bound to the framebuffer
|
||||
virtual void DoClear(Common::Rectangle<u32> rect, Common::Vec4f color, float depth, u8 stencil) = 0;
|
||||
|
||||
/// Returns an immutable reference to the color attachment
|
||||
const TextureHandle& GetColorAttachment() const {
|
||||
return info.color;
|
||||
@ -55,11 +61,6 @@ public:
|
||||
return info.samples;
|
||||
}
|
||||
|
||||
/// Returns the rendering area
|
||||
Rect2D GetDrawRectangle() const {
|
||||
return info.draw_rect;
|
||||
}
|
||||
|
||||
protected:
|
||||
FramebufferInfo info;
|
||||
};
|
||||
|
@ -86,7 +86,8 @@ union BlendState {
|
||||
enum class AttribType : u8 {
|
||||
Float = 0,
|
||||
Int = 1,
|
||||
Short = 2
|
||||
Short = 2,
|
||||
Byte = 3
|
||||
};
|
||||
|
||||
union VertexAttribute {
|
||||
@ -113,6 +114,8 @@ struct PipelineInfo {
|
||||
BlendState blending{};
|
||||
DepthStencilState depth_stencil{};
|
||||
RasterizationState rasterization{};
|
||||
TextureFormat color_attachment = TextureFormat::RGBA8;
|
||||
TextureFormat depth_attachment = TextureFormat::D24S8;
|
||||
|
||||
const u64 Hash() const {
|
||||
return Common::ComputeStructHash64(*this);
|
||||
@ -139,6 +142,10 @@ public:
|
||||
// Binds the sampler in the specified slot
|
||||
virtual void BindSampler(u32 group, u32 slot, SamplerHandle handle) = 0;
|
||||
|
||||
PipelineType GetType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
/// Sets the primitive topology
|
||||
void SetTopology(Pica::TriangleTopology topology) {
|
||||
info.rasterization.topology.Assign(topology);
|
||||
|
1682
src/video_core/common/rasterizer_cache.cpp
Normal file
1682
src/video_core/common/rasterizer_cache.cpp
Normal file
File diff suppressed because it is too large
Load Diff
316
src/video_core/common/rasterizer_cache.h
Normal file
316
src/video_core/common/rasterizer_cache.h
Normal file
@ -0,0 +1,316 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include <boost/icl/interval_set.hpp>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/custom_tex_cache.h"
|
||||
#include "video_core/common/surface_params.h"
|
||||
#include "video_core/common/texture.h"
|
||||
#include "video_core/common/framebuffer.h"
|
||||
#include "video_core/texture/texture_decode.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
class RasterizerCache;
|
||||
class TextureFilterer;
|
||||
|
||||
enum class CubeFace {
|
||||
PositiveX,
|
||||
NegativeX,
|
||||
PositiveY,
|
||||
NegativeY,
|
||||
PositiveZ,
|
||||
NegativeZ,
|
||||
};
|
||||
|
||||
struct TextureCubeConfig {
|
||||
PAddr px;
|
||||
PAddr nx;
|
||||
PAddr py;
|
||||
PAddr ny;
|
||||
PAddr pz;
|
||||
PAddr nz;
|
||||
u32 width;
|
||||
Pica::TexturingRegs::TextureFormat format;
|
||||
|
||||
auto operator <=>(const TextureCubeConfig& other) const = default;
|
||||
};
|
||||
|
||||
using SurfaceSet = std::set<Surface>;
|
||||
|
||||
using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>;
|
||||
|
||||
using SurfaceMap = boost::icl::interval_map<PAddr, Surface, boost::icl::partial_absorber, std::less,
|
||||
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
|
||||
|
||||
using SurfaceCache = boost::icl::interval_map<PAddr, SurfaceSet, boost::icl::partial_absorber, std::less,
|
||||
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
|
||||
|
||||
static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() &&
|
||||
std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(),
|
||||
"Incorrect interval types");
|
||||
|
||||
using SurfaceRect_Tuple = std::tuple<Surface, Common::Rectangle<u32>>;
|
||||
using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, Common::Rectangle<u32>>;
|
||||
|
||||
using PageMap = boost::icl::interval_map<u32, int>;
|
||||
|
||||
enum class ScaleMatch {
|
||||
Exact, // only accept same res scale
|
||||
Upscale, // only allow higher scale than params
|
||||
Ignore // accept every scaled res
|
||||
};
|
||||
|
||||
/**
|
||||
* A watcher that notifies whether a cached surface has been changed. This is useful for caching
|
||||
* surface collection objects, including texture cube and mipmap.
|
||||
*/
|
||||
struct SurfaceWatcher {
|
||||
public:
|
||||
explicit SurfaceWatcher(std::weak_ptr<CachedSurface>&& surface) : surface(std::move(surface)) {}
|
||||
|
||||
// Checks whether the surface has been changed.
|
||||
bool IsValid() const {
|
||||
return !surface.expired() && valid;
|
||||
}
|
||||
|
||||
// Marks that the content of the referencing surface has been updated to the watcher user.
|
||||
void Validate() {
|
||||
ASSERT(!surface.expired());
|
||||
valid = true;
|
||||
}
|
||||
|
||||
// Gets the referencing surface. Returns null if the surface has been destroyed
|
||||
Surface Get() const {
|
||||
return surface.lock();
|
||||
}
|
||||
|
||||
private:
|
||||
friend struct CachedSurface;
|
||||
std::weak_ptr<CachedSurface> surface;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
class RasterizerCache;
|
||||
|
||||
struct CachedSurface : SurfaceParams, std::enable_shared_from_this<CachedSurface> {
|
||||
CachedSurface(RasterizerCache& owner) : owner{owner} {}
|
||||
~CachedSurface();
|
||||
|
||||
// Read/Write data in 3DS memory to/from gl_buffer
|
||||
void LoadBuffer(PAddr load_start, PAddr load_end);
|
||||
void FlushBuffer(PAddr flush_start, PAddr flush_end);
|
||||
|
||||
// Custom texture loading and dumping
|
||||
bool LoadCustomTexture(u64 tex_hash);
|
||||
//void DumpTexture(GLuint target_tex, u64 tex_hash);
|
||||
|
||||
// Upload/Download data in gl_buffer in/to this surface's texture
|
||||
void UploadTexture(Common::Rectangle<u32> rect);
|
||||
void DownloadTexture(const Common::Rectangle<u32>& rect);
|
||||
|
||||
void Fill(Common::Rectangle<u32> rect, const u8* data);
|
||||
|
||||
// Queries the surface for the fill/copy capability
|
||||
bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const;
|
||||
bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const;
|
||||
|
||||
bool IsRegionValid(SurfaceInterval interval) const {
|
||||
return (invalid_regions.find(interval) == invalid_regions.end());
|
||||
}
|
||||
|
||||
bool IsSurfaceFullyInvalid() const {
|
||||
auto interval = GetInterval();
|
||||
return *invalid_regions.equal_range(interval).first == interval;
|
||||
}
|
||||
|
||||
std::shared_ptr<SurfaceWatcher> CreateWatcher() {
|
||||
auto watcher = std::make_shared<SurfaceWatcher>(weak_from_this());
|
||||
watchers.push_front(watcher);
|
||||
return watcher;
|
||||
}
|
||||
|
||||
void InvalidateAllWatcher() {
|
||||
for (const auto& watcher : watchers) {
|
||||
if (auto locked = watcher.lock()) {
|
||||
locked->valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnlinkAllWatcher() {
|
||||
for (const auto& watcher : watchers) {
|
||||
if (auto locked = watcher.lock()) {
|
||||
locked->valid = false;
|
||||
locked->surface.reset();
|
||||
}
|
||||
}
|
||||
watchers.clear();
|
||||
}
|
||||
|
||||
static constexpr unsigned int GetBytesPerPixel(PixelFormat format) {
|
||||
// D24 is almost always 4 byte aligned
|
||||
return format == PixelFormat::Invalid
|
||||
? 0
|
||||
: (format == PixelFormat::D24 || GetFormatType(format) == SurfaceType::Texture)
|
||||
? 4
|
||||
: SurfaceParams::GetFormatBpp(format) / 8;
|
||||
}
|
||||
|
||||
public:
|
||||
bool registered = false;
|
||||
SurfaceRegions invalid_regions;
|
||||
|
||||
u32 fill_size = 0; // Number of bytes to read from fill_data
|
||||
std::array<u8, 4> fill_data;
|
||||
TextureHandle texture;
|
||||
std::vector<u8> gl_buffer;
|
||||
|
||||
// max mipmap level that has been attached to the texture
|
||||
u32 max_level = 0;
|
||||
|
||||
// level_watchers[i] watches the (i+1)-th level mipmap source surface
|
||||
std::array<std::shared_ptr<SurfaceWatcher>, 7> level_watchers;
|
||||
|
||||
bool is_custom = false;
|
||||
Core::CustomTexInfo custom_tex_info;
|
||||
|
||||
private:
|
||||
RasterizerCache& owner;
|
||||
std::list<std::weak_ptr<SurfaceWatcher>> watchers;
|
||||
};
|
||||
|
||||
struct CachedTextureCube {
|
||||
TextureHandle texture;
|
||||
u16 res_scale = 1;
|
||||
std::shared_ptr<SurfaceWatcher> px, nx, py, ny, pz, nz;
|
||||
};
|
||||
|
||||
class BackendBase;
|
||||
|
||||
class RasterizerCache {
|
||||
public:
|
||||
RasterizerCache(std::unique_ptr<BackendBase>& backend);
|
||||
~RasterizerCache();
|
||||
|
||||
// Allocates a 2D texture for a surface possibly reusing an existing one
|
||||
TextureHandle AllocateSurfaceTexture(const TextureInfo& info);
|
||||
|
||||
// Defers destruction of texture handle in case of reuse
|
||||
void RecycleTexture(TextureHandle&& handle);
|
||||
|
||||
// Blit one surface's texture to another
|
||||
bool BlitSurfaces(const Surface& src_surface, const Common::Rectangle<u32>& src_rect,
|
||||
const Surface& dst_surface, const Common::Rectangle<u32>& dst_rect);
|
||||
|
||||
// Copy one surface's region to another
|
||||
void CopySurface(const Surface& src_surface, const Surface& dst_surface,
|
||||
SurfaceInterval copy_interval);
|
||||
|
||||
// Load a texture from 3DS memory to OpenGL and cache it (if not already cached)
|
||||
Surface GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale,
|
||||
bool load_if_create);
|
||||
|
||||
// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from
|
||||
// 3DS memory to OpenGL and caches it (if not already cached)
|
||||
SurfaceRect_Tuple GetSurfaceSubRect(const SurfaceParams& params, ScaleMatch match_res_scale,
|
||||
bool load_if_create);
|
||||
|
||||
// Get a surface based on the texture configuration
|
||||
Surface GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config);
|
||||
Surface GetTextureSurface(const Pica::Texture::TextureInfo& info, u32 max_level = 0);
|
||||
|
||||
// Get a texture cube based on the texture configuration
|
||||
const CachedTextureCube& GetTextureCube(const TextureCubeConfig& config);
|
||||
|
||||
// Get the color and depth surfaces based on the framebuffer configuration
|
||||
SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb,
|
||||
const Common::Rectangle<s32>& viewport_rect);
|
||||
|
||||
// Get a surface that matches the fill config
|
||||
Surface GetFillSurface(const GPU::Regs::MemoryFillConfig& config);
|
||||
|
||||
// Get a surface that matches a "texture copy" display transfer config
|
||||
SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params);
|
||||
|
||||
// Write any cached resources overlapping the region back to memory (if dirty)
|
||||
void FlushRegion(PAddr addr, u32 size, Surface flush_surface = nullptr);
|
||||
|
||||
// Mark region as being invalidated by region_owner (nullptr if 3DS memory)
|
||||
void InvalidateRegion(PAddr addr, u32 size, const Surface& region_owner);
|
||||
|
||||
// Flush all cached resources tracked by this cache manager
|
||||
void FlushAll();
|
||||
|
||||
// Clear all cached resources tracked by this cache manager
|
||||
void ClearAll(bool flush);
|
||||
|
||||
private:
|
||||
void DuplicateSurface(const Surface& src_surface, const Surface& dest_surface);
|
||||
|
||||
/// Update surface's texture for given region when necessary
|
||||
void ValidateSurface(const Surface& surface, PAddr addr, u32 size);
|
||||
|
||||
// Returns false if there is a surface in the cache at the interval with the same bit-width,
|
||||
bool NoUnimplementedReinterpretations(const Surface& surface,
|
||||
SurfaceParams& params,
|
||||
const SurfaceInterval& interval);
|
||||
|
||||
// Return true if a surface with an invalid pixel format exists at the interval
|
||||
bool IntervalHasInvalidPixelFormat(SurfaceParams& params, const SurfaceInterval& interval);
|
||||
|
||||
// Attempt to find a reinterpretable surface in the cache and use it to copy for validation
|
||||
bool ValidateByReinterpretation(const Surface& surface, SurfaceParams& params,
|
||||
const SurfaceInterval& interval);
|
||||
|
||||
// Create a new surface
|
||||
Surface CreateSurface(const SurfaceParams& params);
|
||||
|
||||
// Register surface into the cache
|
||||
void RegisterSurface(const Surface& surface);
|
||||
|
||||
// Remove surface from the cache
|
||||
void UnregisterSurface(const Surface& surface);
|
||||
|
||||
// Increase/decrease the number of surface in pages touching the specified region
|
||||
void UpdatePagesCachedCount(PAddr addr, u32 size, int delta);
|
||||
|
||||
// Fills the entire or part of a surface with the provided color/depth data
|
||||
bool FillSurface(const Surface& surface, const u8* fill_data, Common::Rectangle<u32> fill_rect);
|
||||
|
||||
private:
|
||||
// Textures from destroyed surfaces are stored here to be recyled to reduce allocation overhead
|
||||
std::unordered_multimap<TextureInfo, TextureHandle> host_texture_recycler;
|
||||
std::recursive_mutex mutex;
|
||||
|
||||
// Separate cache for texture cubes
|
||||
std::unordered_map<TextureCubeConfig, CachedTextureCube> texture_cube_cache;
|
||||
|
||||
// Cached surfaces
|
||||
SurfaceCache surface_cache;
|
||||
PageMap cached_pages;
|
||||
SurfaceMap dirty_regions;
|
||||
SurfaceSet remove_surfaces;
|
||||
u16 resolution_scale_factor;
|
||||
|
||||
// Keeping a framebuffer cache is both useful for storing render targets but also
|
||||
// for accelerating texture clear operations
|
||||
std::unordered_map<FramebufferInfo, FramebufferHandle> framebuffer_cache;
|
||||
|
||||
public:
|
||||
std::unique_ptr<BackendBase>& backend;
|
||||
std::unique_ptr<TextureFilterer> texture_filterer;
|
||||
//std::unique_ptr<FormatReinterpreterOpenGL> format_reinterpreter;
|
||||
//std::unique_ptr<TextureDownloaderES> texture_downloader_es;
|
||||
};
|
||||
|
||||
} // namespace VideoCore
|
171
src/video_core/common/surface_params.cpp
Normal file
171
src/video_core/common/surface_params.cpp
Normal file
@ -0,0 +1,171 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "video_core/common/rasterizer_cache.h"
|
||||
#include "video_core/common/surface_params.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const {
|
||||
SurfaceParams params = *this;
|
||||
const u32 tiled_size = is_tiled ? 8 : 1;
|
||||
const u32 stride_tiled_bytes = BytesInPixels(stride * tiled_size);
|
||||
PAddr aligned_start =
|
||||
addr + Common::AlignDown(boost::icl::first(interval) - addr, stride_tiled_bytes);
|
||||
PAddr aligned_end =
|
||||
addr + Common::AlignUp(boost::icl::last_next(interval) - addr, stride_tiled_bytes);
|
||||
|
||||
if (aligned_end - aligned_start > stride_tiled_bytes) {
|
||||
params.addr = aligned_start;
|
||||
params.height = (aligned_end - aligned_start) / BytesInPixels(stride);
|
||||
} else {
|
||||
// 1 row
|
||||
ASSERT(aligned_end - aligned_start == stride_tiled_bytes);
|
||||
const u32 tiled_alignment = BytesInPixels(is_tiled ? 8 * 8 : 1);
|
||||
aligned_start =
|
||||
addr + Common::AlignDown(boost::icl::first(interval) - addr, tiled_alignment);
|
||||
aligned_end =
|
||||
addr + Common::AlignUp(boost::icl::last_next(interval) - addr, tiled_alignment);
|
||||
params.addr = aligned_start;
|
||||
params.width = PixelsInBytes(aligned_end - aligned_start) / tiled_size;
|
||||
params.stride = params.width;
|
||||
params.height = tiled_size;
|
||||
}
|
||||
params.UpdateParams();
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
SurfaceInterval SurfaceParams::GetSubRectInterval(Common::Rectangle<u32> unscaled_rect) const {
|
||||
if (unscaled_rect.GetHeight() == 0 || unscaled_rect.GetWidth() == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (is_tiled) {
|
||||
unscaled_rect.left = Common::AlignDown(unscaled_rect.left, 8) * 8;
|
||||
unscaled_rect.bottom = Common::AlignDown(unscaled_rect.bottom, 8) / 8;
|
||||
unscaled_rect.right = Common::AlignUp(unscaled_rect.right, 8) * 8;
|
||||
unscaled_rect.top = Common::AlignUp(unscaled_rect.top, 8) / 8;
|
||||
}
|
||||
|
||||
const u32 stride_tiled = !is_tiled ? stride : stride * 8;
|
||||
|
||||
const u32 pixel_offset =
|
||||
stride_tiled * (!is_tiled ? unscaled_rect.bottom : (height / 8) - unscaled_rect.top) +
|
||||
unscaled_rect.left;
|
||||
|
||||
const u32 pixels = (unscaled_rect.GetHeight() - 1) * stride_tiled + unscaled_rect.GetWidth();
|
||||
|
||||
return {addr + BytesInPixels(pixel_offset), addr + BytesInPixels(pixel_offset + pixels)};
|
||||
}
|
||||
|
||||
SurfaceInterval SurfaceParams::GetCopyableInterval(const Surface& src_surface) const {
|
||||
SurfaceInterval result{};
|
||||
const auto valid_regions =
|
||||
SurfaceRegions(GetInterval() & src_surface->GetInterval()) - src_surface->invalid_regions;
|
||||
for (auto& valid_interval : valid_regions) {
|
||||
const SurfaceInterval aligned_interval{
|
||||
addr + Common::AlignUp(boost::icl::first(valid_interval) - addr,
|
||||
BytesInPixels(is_tiled ? 8 * 8 : 1)),
|
||||
addr + Common::AlignDown(boost::icl::last_next(valid_interval) - addr,
|
||||
BytesInPixels(is_tiled ? 8 * 8 : 1))};
|
||||
|
||||
if (BytesInPixels(is_tiled ? 8 * 8 : 1) > boost::icl::length(valid_interval) ||
|
||||
boost::icl::length(aligned_interval) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the rectangle within aligned_interval
|
||||
const u32 stride_bytes = BytesInPixels(stride) * (is_tiled ? 8 : 1);
|
||||
SurfaceInterval rect_interval{
|
||||
addr + Common::AlignUp(boost::icl::first(aligned_interval) - addr, stride_bytes),
|
||||
addr + Common::AlignDown(boost::icl::last_next(aligned_interval) - addr, stride_bytes),
|
||||
};
|
||||
if (boost::icl::first(rect_interval) > boost::icl::last_next(rect_interval)) {
|
||||
// 1 row
|
||||
rect_interval = aligned_interval;
|
||||
} else if (boost::icl::length(rect_interval) == 0) {
|
||||
// 2 rows that do not make a rectangle, return the larger one
|
||||
const SurfaceInterval row1{boost::icl::first(aligned_interval),
|
||||
boost::icl::first(rect_interval)};
|
||||
const SurfaceInterval row2{boost::icl::first(rect_interval),
|
||||
boost::icl::last_next(aligned_interval)};
|
||||
rect_interval = (boost::icl::length(row1) > boost::icl::length(row2)) ? row1 : row2;
|
||||
}
|
||||
|
||||
if (boost::icl::length(rect_interval) > boost::icl::length(result)) {
|
||||
result = rect_interval;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> SurfaceParams::GetSubRect(const SurfaceParams& sub_surface) const {
|
||||
const u32 begin_pixel_index = PixelsInBytes(sub_surface.addr - addr);
|
||||
|
||||
if (is_tiled) {
|
||||
const int x0 = (begin_pixel_index % (stride * 8)) / 8;
|
||||
const int y0 = (begin_pixel_index / (stride * 8)) * 8;
|
||||
// Top to bottom
|
||||
return Common::Rectangle<u32>(x0, height - y0, x0 + sub_surface.width,
|
||||
height - (y0 + sub_surface.height));
|
||||
}
|
||||
|
||||
const int x0 = begin_pixel_index % stride;
|
||||
const int y0 = begin_pixel_index / stride;
|
||||
// Bottom to top
|
||||
return Common::Rectangle<u32>(x0, y0 + sub_surface.height, x0 + sub_surface.width, y0);
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> SurfaceParams::GetScaledSubRect(const SurfaceParams& sub_surface) const {
|
||||
auto rect = GetSubRect(sub_surface);
|
||||
rect.left = rect.left * res_scale;
|
||||
rect.right = rect.right * res_scale;
|
||||
rect.top = rect.top * res_scale;
|
||||
rect.bottom = rect.bottom * res_scale;
|
||||
return rect;
|
||||
}
|
||||
|
||||
bool SurfaceParams::ExactMatch(const SurfaceParams& other_surface) const {
|
||||
return std::tie(other_surface.addr, other_surface.width, other_surface.height,
|
||||
other_surface.stride, other_surface.pixel_format, other_surface.is_tiled) ==
|
||||
std::tie(addr, width, height, stride, pixel_format, is_tiled) &&
|
||||
pixel_format != PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
bool SurfaceParams::CanSubRect(const SurfaceParams& sub_surface) const {
|
||||
return sub_surface.addr >= addr && sub_surface.end <= end &&
|
||||
sub_surface.pixel_format == pixel_format && pixel_format != PixelFormat::Invalid &&
|
||||
sub_surface.is_tiled == is_tiled &&
|
||||
(sub_surface.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 &&
|
||||
(sub_surface.stride == stride || sub_surface.height <= (is_tiled ? 8u : 1u)) &&
|
||||
GetSubRect(sub_surface).right <= stride;
|
||||
}
|
||||
|
||||
bool SurfaceParams::CanExpand(const SurfaceParams& expanded_surface) const {
|
||||
return pixel_format != PixelFormat::Invalid && pixel_format == expanded_surface.pixel_format &&
|
||||
addr <= expanded_surface.end && expanded_surface.addr <= end &&
|
||||
is_tiled == expanded_surface.is_tiled && stride == expanded_surface.stride &&
|
||||
(std::max(expanded_surface.addr, addr) - std::min(expanded_surface.addr, addr)) %
|
||||
BytesInPixels(stride * (is_tiled ? 8 : 1)) ==
|
||||
0;
|
||||
}
|
||||
|
||||
bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const {
|
||||
if (pixel_format == PixelFormat::Invalid || addr > texcopy_params.addr ||
|
||||
end < texcopy_params.end) {
|
||||
return false;
|
||||
}
|
||||
if (texcopy_params.width != texcopy_params.stride) {
|
||||
const u32 tile_stride = BytesInPixels(stride * (is_tiled ? 8 : 1));
|
||||
return (texcopy_params.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 &&
|
||||
texcopy_params.width % BytesInPixels(is_tiled ? 64 : 1) == 0 &&
|
||||
(texcopy_params.height == 1 || texcopy_params.stride == tile_stride) &&
|
||||
((texcopy_params.addr - addr) % tile_stride) + texcopy_params.width <= tile_stride;
|
||||
}
|
||||
return FromInterval(texcopy_params.GetInterval()).GetInterval() == texcopy_params.GetInterval();
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
270
src/video_core/common/surface_params.h
Normal file
270
src/video_core/common/surface_params.h
Normal file
@ -0,0 +1,270 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <boost/icl/interval.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
struct CachedSurface;
|
||||
using Surface = std::shared_ptr<CachedSurface>;
|
||||
|
||||
using SurfaceInterval = boost::icl::right_open_interval<PAddr>;
|
||||
|
||||
struct SurfaceParams {
|
||||
private:
|
||||
static constexpr std::array<unsigned int, 18> BPP_TABLE = {
|
||||
32, // RGBA8
|
||||
24, // RGB8
|
||||
16, // RGB5A1
|
||||
16, // RGB565
|
||||
16, // RGBA4
|
||||
16, // IA8
|
||||
16, // RG8
|
||||
8, // I8
|
||||
8, // A8
|
||||
8, // IA4
|
||||
4, // I4
|
||||
4, // A4
|
||||
4, // ETC1
|
||||
8, // ETC1A4
|
||||
16, // D16
|
||||
0,
|
||||
24, // D24
|
||||
32, // D24S8
|
||||
};
|
||||
|
||||
public:
|
||||
enum class PixelFormat {
|
||||
// First 5 formats are shared between textures and color buffers
|
||||
RGBA8 = 0,
|
||||
RGB8 = 1,
|
||||
RGB5A1 = 2,
|
||||
RGB565 = 3,
|
||||
RGBA4 = 4,
|
||||
|
||||
// Texture-only formats
|
||||
IA8 = 5,
|
||||
RG8 = 6,
|
||||
I8 = 7,
|
||||
A8 = 8,
|
||||
IA4 = 9,
|
||||
I4 = 10,
|
||||
A4 = 11,
|
||||
ETC1 = 12,
|
||||
ETC1A4 = 13,
|
||||
|
||||
// Depth buffer-only formats
|
||||
D16 = 14,
|
||||
// gap
|
||||
D24 = 16,
|
||||
D24S8 = 17,
|
||||
|
||||
Invalid = 255,
|
||||
};
|
||||
|
||||
enum class SurfaceType {
|
||||
Color = 0,
|
||||
Texture = 1,
|
||||
Depth = 2,
|
||||
DepthStencil = 3,
|
||||
Fill = 4,
|
||||
Invalid = 5
|
||||
};
|
||||
|
||||
static constexpr unsigned int GetFormatBpp(PixelFormat format) {
|
||||
const auto format_idx = static_cast<std::size_t>(format);
|
||||
DEBUG_ASSERT_MSG(format_idx < BPP_TABLE.size(), "Invalid pixel format {}", format_idx);
|
||||
return BPP_TABLE[format_idx];
|
||||
}
|
||||
|
||||
unsigned int GetFormatBpp() const {
|
||||
return GetFormatBpp(pixel_format);
|
||||
}
|
||||
|
||||
static std::string_view PixelFormatAsString(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::RGBA8:
|
||||
return "RGBA8";
|
||||
case PixelFormat::RGB8:
|
||||
return "RGB8";
|
||||
case PixelFormat::RGB5A1:
|
||||
return "RGB5A1";
|
||||
case PixelFormat::RGB565:
|
||||
return "RGB565";
|
||||
case PixelFormat::RGBA4:
|
||||
return "RGBA4";
|
||||
case PixelFormat::IA8:
|
||||
return "IA8";
|
||||
case PixelFormat::RG8:
|
||||
return "RG8";
|
||||
case PixelFormat::I8:
|
||||
return "I8";
|
||||
case PixelFormat::A8:
|
||||
return "A8";
|
||||
case PixelFormat::IA4:
|
||||
return "IA4";
|
||||
case PixelFormat::I4:
|
||||
return "I4";
|
||||
case PixelFormat::A4:
|
||||
return "A4";
|
||||
case PixelFormat::ETC1:
|
||||
return "ETC1";
|
||||
case PixelFormat::ETC1A4:
|
||||
return "ETC1A4";
|
||||
case PixelFormat::D16:
|
||||
return "D16";
|
||||
case PixelFormat::D24:
|
||||
return "D24";
|
||||
case PixelFormat::D24S8:
|
||||
return "D24S8";
|
||||
default:
|
||||
return "Not a real pixel format";
|
||||
}
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) {
|
||||
return ((unsigned int)format < 14) ? (PixelFormat)format : PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) {
|
||||
return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) {
|
||||
return ((unsigned int)format < 4) ? (PixelFormat)((unsigned int)format + 14)
|
||||
: PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) {
|
||||
switch (format) {
|
||||
// RGB565 and RGB5A1 are switched in PixelFormat compared to ColorFormat
|
||||
case GPU::Regs::PixelFormat::RGB565:
|
||||
return PixelFormat::RGB565;
|
||||
case GPU::Regs::PixelFormat::RGB5A1:
|
||||
return PixelFormat::RGB5A1;
|
||||
default:
|
||||
return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
static bool CheckFormatsBlittable(PixelFormat pixel_format_a, PixelFormat pixel_format_b) {
|
||||
SurfaceType a_type = GetFormatType(pixel_format_a);
|
||||
SurfaceType b_type = GetFormatType(pixel_format_b);
|
||||
|
||||
if ((a_type == SurfaceType::Color || a_type == SurfaceType::Texture) &&
|
||||
(b_type == SurfaceType::Color || b_type == SurfaceType::Texture)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a_type == SurfaceType::Depth && b_type == SurfaceType::Depth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a_type == SurfaceType::DepthStencil && b_type == SurfaceType::DepthStencil) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr SurfaceType GetFormatType(PixelFormat pixel_format) {
|
||||
if ((unsigned int)pixel_format < 5) {
|
||||
return SurfaceType::Color;
|
||||
}
|
||||
|
||||
if ((unsigned int)pixel_format < 14) {
|
||||
return SurfaceType::Texture;
|
||||
}
|
||||
|
||||
if (pixel_format == PixelFormat::D16 || pixel_format == PixelFormat::D24) {
|
||||
return SurfaceType::Depth;
|
||||
}
|
||||
|
||||
if (pixel_format == PixelFormat::D24S8) {
|
||||
return SurfaceType::DepthStencil;
|
||||
}
|
||||
|
||||
return SurfaceType::Invalid;
|
||||
}
|
||||
|
||||
/// Update the params "size", "end" and "type" from the already set "addr", "width", "height"
|
||||
/// and "pixel_format"
|
||||
void UpdateParams() {
|
||||
if (stride == 0) {
|
||||
stride = width;
|
||||
}
|
||||
type = GetFormatType(pixel_format);
|
||||
size = !is_tiled ? BytesInPixels(stride * (height - 1) + width)
|
||||
: BytesInPixels(stride * 8 * (height / 8 - 1) + width * 8);
|
||||
end = addr + size;
|
||||
}
|
||||
|
||||
SurfaceInterval GetInterval() const {
|
||||
return SurfaceInterval(addr, end);
|
||||
}
|
||||
|
||||
// Returns the outer rectangle containing "interval"
|
||||
SurfaceParams FromInterval(SurfaceInterval interval) const;
|
||||
|
||||
SurfaceInterval GetSubRectInterval(Common::Rectangle<u32> unscaled_rect) const;
|
||||
|
||||
// Returns the region of the biggest valid rectange within interval
|
||||
SurfaceInterval GetCopyableInterval(const Surface& src_surface) const;
|
||||
|
||||
u32 GetScaledWidth() const {
|
||||
return width * res_scale;
|
||||
}
|
||||
|
||||
u32 GetScaledHeight() const {
|
||||
return height * res_scale;
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> GetRect() const {
|
||||
return {0, height, width, 0};
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> GetScaledRect() const {
|
||||
return {0, GetScaledHeight(), GetScaledWidth(), 0};
|
||||
}
|
||||
|
||||
u32 PixelsInBytes(u32 size) const {
|
||||
return size * CHAR_BIT / GetFormatBpp(pixel_format);
|
||||
}
|
||||
|
||||
u32 BytesInPixels(u32 pixels) const {
|
||||
return pixels * GetFormatBpp(pixel_format) / CHAR_BIT;
|
||||
}
|
||||
|
||||
bool ExactMatch(const SurfaceParams& other_surface) const;
|
||||
bool CanSubRect(const SurfaceParams& sub_surface) const;
|
||||
bool CanExpand(const SurfaceParams& expanded_surface) const;
|
||||
bool CanTexCopy(const SurfaceParams& texcopy_params) const;
|
||||
|
||||
Common::Rectangle<u32> GetSubRect(const SurfaceParams& sub_surface) const;
|
||||
Common::Rectangle<u32> GetScaledSubRect(const SurfaceParams& sub_surface) const;
|
||||
|
||||
PAddr addr = 0;
|
||||
PAddr end = 0;
|
||||
u32 size = 0;
|
||||
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
u32 stride = 0;
|
||||
u16 res_scale = 1;
|
||||
|
||||
bool is_tiled = false;
|
||||
PixelFormat pixel_format = PixelFormat::Invalid;
|
||||
SurfaceType type = SurfaceType::Invalid;
|
||||
};
|
||||
|
||||
} // namespace VideoCore
|
@ -7,6 +7,7 @@
|
||||
#include <span>
|
||||
#include "common/hash.h"
|
||||
#include "common/intrusive_ptr.h"
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
|
||||
namespace VideoCore {
|
||||
@ -50,6 +51,12 @@ enum class TextureViewType : u8 {
|
||||
* @param width, height are the extent of the rectangle
|
||||
*/
|
||||
struct Rect2D {
|
||||
Rect2D() = default;
|
||||
Rect2D(s32 x, s32 y, u32 width, u32 height) :
|
||||
x(x), y(y), width(width), height(height) {}
|
||||
Rect2D(Common::Rectangle<u32> rect) :
|
||||
x(rect.left), y(rect.bottom), width(rect.GetWidth()), height(rect.GetHeight()) {}
|
||||
|
||||
s32 x = 0;
|
||||
s32 y = 0;
|
||||
u32 width = 0;
|
||||
@ -67,6 +74,10 @@ struct TextureInfo {
|
||||
TextureViewType view_type = TextureViewType::Undefined;
|
||||
TextureFormat format = TextureFormat::Undefined;
|
||||
|
||||
void UpdateMipLevels() {
|
||||
levels = std::log2(std::max(width, height)) + 1;
|
||||
}
|
||||
|
||||
const u64 Hash() const {
|
||||
return Common::ComputeStructHash64(*this);
|
||||
}
|
||||
@ -84,39 +95,52 @@ public:
|
||||
TextureBase(const TextureInfo& info) : info(info) {}
|
||||
virtual ~TextureBase() = default;
|
||||
|
||||
/// Uploads pixel data to the GPU memory
|
||||
// Disable copy constructor
|
||||
TextureBase(const TextureBase&) = delete;
|
||||
TextureBase& operator=(const TextureBase&) = delete;
|
||||
|
||||
// Uploads pixel data to the GPU memory
|
||||
virtual void Upload(Rect2D rectangle, u32 stride, std::span<const u8> data,
|
||||
u32 level = 0) {};
|
||||
|
||||
/// Downloads pixel data from GPU memory
|
||||
// Downloads pixel data from GPU memory
|
||||
virtual void Download(Rect2D rectangle, u32 stride, std::span<u8> data,
|
||||
u32 level = 0) {};
|
||||
|
||||
/// Copies the rectangle area specified to the destionation texture
|
||||
virtual void BlitTo(TextureHandle dest, Rect2D src_rectangle, Rect2D dest_rect,
|
||||
u32 src_level = 0, u32 dest_level = 0) {};
|
||||
// Copies the rectangle area specified to the destionation texture
|
||||
virtual void BlitTo(TextureHandle dest, Rect2D source_rect, Rect2D dest_rect,
|
||||
u32 src_level = 0, u32 dest_level = 0,
|
||||
u32 src_layer = 0, u32 dest_layer = 0) {};
|
||||
|
||||
/// Returns the unique texture identifier
|
||||
// Generates all possible mipmaps from the texture
|
||||
virtual void GenerateMipmaps() {};
|
||||
|
||||
// Returns the texture info structure
|
||||
TextureInfo GetInfo() const {
|
||||
return info;
|
||||
}
|
||||
|
||||
// Returns the unique texture identifier
|
||||
const u64 GetHash() const {
|
||||
return info.Hash();
|
||||
}
|
||||
|
||||
/// Returns the width of the texture
|
||||
// Returns the width of the texture
|
||||
u16 GetWidth() const {
|
||||
return info.width;
|
||||
}
|
||||
|
||||
/// Returns the height of the texture
|
||||
// Returns the height of the texture
|
||||
u16 GetHeight() const {
|
||||
return info.height;
|
||||
}
|
||||
|
||||
/// Returns the number of mipmap levels allocated
|
||||
// Returns the number of mipmap levels allocated
|
||||
u16 GetMipLevels() const {
|
||||
return info.levels;
|
||||
}
|
||||
|
||||
/// Returns the pixel format
|
||||
// Returns the pixel format
|
||||
TextureFormat GetFormat() const {
|
||||
return info.format;
|
||||
}
|
||||
@ -146,6 +170,10 @@ public:
|
||||
SamplerBase(SamplerInfo info) : info(info) {}
|
||||
virtual ~SamplerBase() = default;
|
||||
|
||||
// Disable copy constructor
|
||||
SamplerBase(const SamplerBase&) = delete;
|
||||
SamplerBase& operator=(const SamplerBase&) = delete;
|
||||
|
||||
protected:
|
||||
SamplerInfo info{};
|
||||
};
|
||||
|
@ -10,20 +10,20 @@
|
||||
namespace Pica {
|
||||
|
||||
template <typename VertexType>
|
||||
PrimitiveAssembler<VertexType>::PrimitiveAssembler(PipelineRegs::TriangleTopology topology)
|
||||
PrimitiveAssembler<VertexType>::PrimitiveAssembler(Pica::TriangleTopology topology)
|
||||
: topology(topology) {}
|
||||
|
||||
template <typename VertexType>
|
||||
void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx,
|
||||
const TriangleHandler& triangle_handler) {
|
||||
switch (topology) {
|
||||
case PipelineRegs::TriangleTopology::List:
|
||||
case PipelineRegs::TriangleTopology::Shader:
|
||||
case Pica::TriangleTopology::List:
|
||||
case Pica::TriangleTopology::Shader:
|
||||
if (buffer_index < 2) {
|
||||
buffer[buffer_index++] = vtx;
|
||||
} else {
|
||||
buffer_index = 0;
|
||||
if (topology == PipelineRegs::TriangleTopology::Shader && winding) {
|
||||
if (topology == Pica::TriangleTopology::Shader && winding) {
|
||||
triangle_handler(buffer[1], buffer[0], vtx);
|
||||
winding = false;
|
||||
} else {
|
||||
@ -32,8 +32,8 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx,
|
||||
}
|
||||
break;
|
||||
|
||||
case PipelineRegs::TriangleTopology::Strip:
|
||||
case PipelineRegs::TriangleTopology::Fan:
|
||||
case Pica::TriangleTopology::Strip:
|
||||
case Pica::TriangleTopology::Fan:
|
||||
if (strip_ready)
|
||||
triangle_handler(buffer[0], buffer[1], vtx);
|
||||
|
||||
@ -41,9 +41,9 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx,
|
||||
|
||||
strip_ready |= (buffer_index == 1);
|
||||
|
||||
if (topology == PipelineRegs::TriangleTopology::Strip)
|
||||
if (topology == Pica::TriangleTopology::Strip)
|
||||
buffer_index = !buffer_index;
|
||||
else if (topology == PipelineRegs::TriangleTopology::Fan)
|
||||
else if (topology == Pica::TriangleTopology::Fan)
|
||||
buffer_index = 1;
|
||||
break;
|
||||
|
||||
@ -66,7 +66,7 @@ void PrimitiveAssembler<VertexType>::Reset() {
|
||||
}
|
||||
|
||||
template <typename VertexType>
|
||||
void PrimitiveAssembler<VertexType>::Reconfigure(PipelineRegs::TriangleTopology topology) {
|
||||
void PrimitiveAssembler<VertexType>::Reconfigure(Pica::TriangleTopology topology) {
|
||||
Reset();
|
||||
this->topology = topology;
|
||||
}
|
||||
@ -77,7 +77,7 @@ bool PrimitiveAssembler<VertexType>::IsEmpty() const {
|
||||
}
|
||||
|
||||
template <typename VertexType>
|
||||
PipelineRegs::TriangleTopology PrimitiveAssembler<VertexType>::GetTopology() const {
|
||||
Pica::TriangleTopology PrimitiveAssembler<VertexType>::GetTopology() const {
|
||||
return topology;
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,7 @@ struct PrimitiveAssembler {
|
||||
using TriangleHandler =
|
||||
std::function<void(const VertexType& v0, const VertexType& v1, const VertexType& v2)>;
|
||||
|
||||
explicit PrimitiveAssembler(
|
||||
PipelineRegs::TriangleTopology topology = PipelineRegs::TriangleTopology::List);
|
||||
explicit PrimitiveAssembler(Pica::TriangleTopology topology = Pica::TriangleTopology::List);
|
||||
|
||||
/*
|
||||
* Queues a vertex, builds primitives from the vertex queue according to the given
|
||||
@ -46,7 +45,7 @@ struct PrimitiveAssembler {
|
||||
/**
|
||||
* Reconfigures the PrimitiveAssembler to use a different triangle topology.
|
||||
*/
|
||||
void Reconfigure(PipelineRegs::TriangleTopology topology);
|
||||
void Reconfigure(Pica::TriangleTopology topology);
|
||||
|
||||
/**
|
||||
* Returns whether the PrimitiveAssembler has an empty internal buffer.
|
||||
@ -56,10 +55,10 @@ struct PrimitiveAssembler {
|
||||
/**
|
||||
* Returns the current topology.
|
||||
*/
|
||||
PipelineRegs::TriangleTopology GetTopology() const;
|
||||
Pica::TriangleTopology GetTopology() const;
|
||||
|
||||
private:
|
||||
PipelineRegs::TriangleTopology topology;
|
||||
Pica::TriangleTopology topology;
|
||||
|
||||
int buffer_index = 0;
|
||||
std::array<VertexType, 2> buffer;
|
||||
|
@ -8,9 +8,38 @@
|
||||
#include "video_core/renderer_vulkan/vk_backend.h"
|
||||
#include "video_core/renderer_vulkan/vk_buffer.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture.h"
|
||||
#include "video_core/renderer_vulkan/vk_framebuffer.h"
|
||||
|
||||
namespace VideoCore::Vulkan {
|
||||
|
||||
constexpr vk::PipelineBindPoint ToVkPipelineBindPoint(PipelineType type) {
|
||||
switch (type) {
|
||||
case PipelineType::Graphics:
|
||||
return vk::PipelineBindPoint::eGraphics;
|
||||
case PipelineType::Compute:
|
||||
return vk::PipelineBindPoint::eCompute;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr vk::Rect2D ToVkRect2D(Rect2D rect) {
|
||||
return vk::Rect2D{
|
||||
.offset = vk::Offset2D{rect.x, rect.y},
|
||||
.extent = vk::Extent2D{rect.width, rect.height}
|
||||
};
|
||||
}
|
||||
|
||||
constexpr vk::IndexType ToVkIndexType(AttribType type) {
|
||||
switch (type) {
|
||||
case AttribType::Short:
|
||||
return vk::IndexType::eUint16;
|
||||
case AttribType::Int:
|
||||
return vk::IndexType::eUint32;
|
||||
default:
|
||||
LOG_CRITICAL(Render_Vulkan, "Unknown index type {}!", type);
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
Backend::Backend(Frontend::EmuWindow& window) : BackendBase(window),
|
||||
instance(window), swapchain(instance, instance.GetSurface()),
|
||||
scheduler(instance) {
|
||||
@ -24,6 +53,7 @@ Backend::Backend(Frontend::EmuWindow& window) : BackendBase(window),
|
||||
|
||||
// Pre-create all needed renderpasses by the renderer
|
||||
constexpr std::array color_formats = {
|
||||
vk::Format::eUndefined,
|
||||
vk::Format::eR8G8B8A8Unorm,
|
||||
vk::Format::eR8G8B8Unorm,
|
||||
vk::Format::eR5G5B5A1UnormPack16,
|
||||
@ -32,18 +62,41 @@ Backend::Backend(Frontend::EmuWindow& window) : BackendBase(window),
|
||||
};
|
||||
|
||||
constexpr std::array depth_stencil_formats = {
|
||||
vk::Format::eUndefined,
|
||||
vk::Format::eD16Unorm,
|
||||
vk::Format::eX8D24UnormPack32,
|
||||
vk::Format::eD24UnormS8Uint,
|
||||
};
|
||||
|
||||
// Create all required renderpasses
|
||||
for (u32 color = 0; color < MAX_COLOR_FORMATS; color++) {
|
||||
for (u32 depth = 0; depth < MAX_DEPTH_FORMATS; depth++) {
|
||||
for (u32 color = 0; color <= MAX_COLOR_FORMATS; color++) {
|
||||
for (u32 depth = 0; depth <= MAX_DEPTH_FORMATS; depth++) {
|
||||
if (color == 0 && depth == 0) continue;
|
||||
|
||||
u32 index = color * MAX_COLOR_FORMATS + depth;
|
||||
renderpass_cache[index] = CreateRenderPass(color_formats[color], depth_stencil_formats[depth]);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::array pool_sizes = {
|
||||
vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, 1024},
|
||||
vk::DescriptorPoolSize{vk::DescriptorType::eUniformBufferDynamic, 1024},
|
||||
vk::DescriptorPoolSize{vk::DescriptorType::eSampledImage, 2048},
|
||||
vk::DescriptorPoolSize{vk::DescriptorType::eSampler, 2048},
|
||||
vk::DescriptorPoolSize{vk::DescriptorType::eUniformTexelBuffer, 1024}
|
||||
};
|
||||
|
||||
const vk::DescriptorPoolCreateInfo pool_info = {
|
||||
.maxSets = 2048,
|
||||
.poolSizeCount = pool_sizes.size(),
|
||||
.pPoolSizes = pool_sizes.data()
|
||||
};
|
||||
|
||||
// Create descriptor pools
|
||||
vk::Device device = instance.GetDevice();
|
||||
for (u32 pool = 0; pool < SCHEDULER_COMMAND_COUNT; pool++) {
|
||||
descriptor_pools[pool] = device.createDescriptorPool(pool_info);
|
||||
}
|
||||
}
|
||||
|
||||
Backend::~Backend() {
|
||||
@ -59,52 +112,118 @@ Backend::~Backend() {
|
||||
*/
|
||||
BufferHandle Backend::CreateBuffer(BufferInfo info) {
|
||||
static ObjectPool<Buffer> buffer_pool;
|
||||
return IntrusivePtr<Buffer>{buffer_pool.Allocate(info)};
|
||||
return BufferHandle{buffer_pool.Allocate(instance, scheduler, info)};
|
||||
}
|
||||
|
||||
FramebufferHandle Backend::CreateFramebuffer(FramebufferInfo info) {
|
||||
static ObjectPool<Framebuffer> framebuffer_pool;
|
||||
|
||||
// Get renderpass
|
||||
TextureFormat color = info.color.IsValid() ? info.color->GetFormat() : TextureFormat::Undefined;
|
||||
TextureFormat depth = info.depth_stencil.IsValid() ? info.depth_stencil->GetFormat() : TextureFormat::Undefined;
|
||||
vk::RenderPass renderpass = GetRenderPass(color, depth);
|
||||
|
||||
return FramebufferHandle{framebuffer_pool.Allocate(instance, info, renderpass)};
|
||||
}
|
||||
|
||||
TextureHandle Backend::CreateTexture(TextureInfo info) {
|
||||
static ObjectPool<Texture> texture_pool;
|
||||
return IntrusivePtr<Texture>{texture_pool.Allocate(info)};
|
||||
return TextureHandle{texture_pool.Allocate(instance, scheduler, info)};
|
||||
}
|
||||
|
||||
PipelineHandle Backend::CreatePipeline(PipelineType type, PipelineInfo info) {
|
||||
static ObjectPool<Pipeline> pipeline_pool;
|
||||
|
||||
// Find a pipeline layout first
|
||||
// Get renderpass
|
||||
vk::RenderPass renderpass = GetRenderPass(info.color_attachment, info.depth_attachment);
|
||||
|
||||
// Find a pipeline layout first
|
||||
if (auto iter = pipeline_layouts.find(info.layout); iter != pipeline_layouts.end()) {
|
||||
PipelineLayout& layout = iter->second;
|
||||
|
||||
return IntrusivePtr<Pipeline>{pipeline_pool.Allocate(instance, layout, type, info, cache)};
|
||||
return PipelineHandle{pipeline_pool.Allocate(instance, layout, type, info, renderpass, cache)};
|
||||
}
|
||||
|
||||
// Create the layout
|
||||
auto result = pipeline_layouts.emplace(info.layout, PipelineLayout{instance, info.layout});
|
||||
return IntrusivePtr<Pipeline>{pipeline_pool.Allocate(instance, result.first->second, type, info, cache)};
|
||||
return PipelineHandle{pipeline_pool.Allocate(instance, result.first->second, type, info, renderpass, cache)};
|
||||
}
|
||||
|
||||
SamplerHandle Backend::CreateSampler(SamplerInfo info) {
|
||||
static ObjectPool<Sampler> sampler_pool;
|
||||
return IntrusivePtr<Sampler>{sampler_pool.Allocate(info)};
|
||||
return SamplerHandle{sampler_pool.Allocate(info)};
|
||||
}
|
||||
|
||||
void Backend::Draw(PipelineHandle pipeline, FramebufferHandle draw_framebuffer,
|
||||
void Backend::Draw(PipelineHandle pipeline_handle, FramebufferHandle draw_framebuffer,
|
||||
BufferHandle vertex_buffer,
|
||||
u32 base_vertex, u32 num_vertices) {
|
||||
// Bind descriptor sets
|
||||
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
|
||||
BindDescriptorSets(pipeline_handle);
|
||||
|
||||
Buffer* vertex = static_cast<Buffer*>(vertex_buffer.Get());
|
||||
command_buffer.bindVertexBuffers(0, vertex->GetHandle(), {0});
|
||||
// Bind vertex buffer
|
||||
const Buffer* vertex = static_cast<const Buffer*>(vertex_buffer.Get());
|
||||
command_buffer.bindVertexBuffers(0, vertex->GetHandle(), vertex->GetBindOffset());
|
||||
|
||||
// Begin renderpass
|
||||
const Framebuffer* framebuffer = static_cast<const Framebuffer*>(draw_framebuffer.Get());
|
||||
const vk::RenderPassBeginInfo renderpass_begin = {
|
||||
.renderPass = framebuffer->GetRenderpass(),
|
||||
.framebuffer = framebuffer->GetHandle(),
|
||||
.renderArea = ToVkRect2D(framebuffer->GetDrawRectangle()),
|
||||
.clearValueCount = 0,
|
||||
.pClearValues = nullptr
|
||||
};
|
||||
|
||||
command_buffer.beginRenderPass(renderpass_begin, vk::SubpassContents::eInline);
|
||||
|
||||
// Bind pipeline
|
||||
const Pipeline* pipeline = static_cast<const Pipeline*>(pipeline_handle.Get());
|
||||
command_buffer.bindPipeline(ToVkPipelineBindPoint(pipeline->GetType()), pipeline->GetHandle());
|
||||
|
||||
// Submit draw
|
||||
command_buffer.draw(num_vertices, 1, base_vertex, 0);
|
||||
|
||||
// End renderpass
|
||||
command_buffer.endRenderPass();
|
||||
}
|
||||
|
||||
void Backend::DrawIndexed(PipelineHandle pipeline, FramebufferHandle draw_framebuffer,
|
||||
BufferHandle vertex_buffer, BufferHandle index_buffer,
|
||||
void Backend::DrawIndexed(PipelineHandle pipeline_handle, FramebufferHandle draw_framebuffer,
|
||||
BufferHandle vertex_buffer, BufferHandle index_buffer, AttribType index_type,
|
||||
u32 base_index, u32 num_indices, u32 base_vertex) {
|
||||
// Bind descriptor sets
|
||||
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
|
||||
BindDescriptorSets(pipeline_handle);
|
||||
|
||||
// Bind vertex buffer
|
||||
const Buffer* vertex = static_cast<const Buffer*>(vertex_buffer.Get());
|
||||
command_buffer.bindVertexBuffers(0, vertex->GetHandle(), vertex->GetBindOffset());
|
||||
|
||||
// Bind index buffer
|
||||
const Buffer* index = static_cast<const Buffer*>(index_buffer.Get());
|
||||
command_buffer.bindIndexBuffer(index->GetHandle(), index->GetBindOffset(), ToVkIndexType(index_type));
|
||||
|
||||
// Begin renderpass
|
||||
const Framebuffer* framebuffer = static_cast<const Framebuffer*>(draw_framebuffer.Get());
|
||||
const vk::RenderPassBeginInfo renderpass_begin = {
|
||||
.renderPass = framebuffer->GetRenderpass(),
|
||||
.framebuffer = framebuffer->GetHandle(),
|
||||
.renderArea = ToVkRect2D(framebuffer->GetDrawRectangle()),
|
||||
.clearValueCount = 0,
|
||||
.pClearValues = nullptr
|
||||
};
|
||||
|
||||
command_buffer.beginRenderPass(renderpass_begin, vk::SubpassContents::eInline);
|
||||
|
||||
// Bind pipeline
|
||||
const Pipeline* pipeline = static_cast<const Pipeline*>(pipeline_handle.Get());
|
||||
command_buffer.bindPipeline(ToVkPipelineBindPoint(pipeline->GetType()), pipeline->GetHandle());
|
||||
|
||||
// Submit draw
|
||||
command_buffer.drawIndexed(num_indices, 1, base_index, base_vertex, 0);
|
||||
|
||||
// End renderpass
|
||||
command_buffer.endRenderPass();
|
||||
|
||||
}
|
||||
|
||||
@ -175,4 +294,39 @@ vk::RenderPass Backend::CreateRenderPass(vk::Format color, vk::Format depth) con
|
||||
return device.createRenderPass(renderpass_info);
|
||||
}
|
||||
|
||||
vk::RenderPass Backend::GetRenderPass(TextureFormat color, TextureFormat depth) const {
|
||||
u32 color_index = color != TextureFormat::Undefined ? static_cast<u32>(color) + 1 : 0;
|
||||
u32 depth_index = depth != TextureFormat::Undefined ? static_cast<u32>(depth) - 4 : 0;
|
||||
return renderpass_cache[color_index * MAX_COLOR_FORMATS + depth_index];
|
||||
}
|
||||
|
||||
void Backend::BindDescriptorSets(PipelineHandle handle) {
|
||||
Pipeline* pipeline = static_cast<Pipeline*>(handle.Get());
|
||||
PipelineLayout& pipeline_layout = pipeline->GetOwner();
|
||||
|
||||
// Allocate required descriptor sets
|
||||
// TODO: Maybe cache them?
|
||||
u32 pool_index = scheduler.GetCurrentSlotIndex();
|
||||
const vk::DescriptorSetAllocateInfo alloc_info = {
|
||||
.descriptorPool = descriptor_pools[pool_index],
|
||||
.descriptorSetCount = pipeline_layout.GetDescriptorSetLayoutCount(),
|
||||
.pSetLayouts = pipeline_layout.GetDescriptorSetLayouts()
|
||||
};
|
||||
|
||||
vk::Device device = instance.GetDevice();
|
||||
auto descriptor_sets = device.allocateDescriptorSets(alloc_info);
|
||||
|
||||
// Write data to the descriptor sets
|
||||
for (u32 set = 0; set < descriptor_sets.size(); set++) {
|
||||
device.updateDescriptorSetWithTemplate(descriptor_sets[set],
|
||||
pipeline_layout.GetUpdateTemplate(set),
|
||||
pipeline_layout.GetData(set));
|
||||
}
|
||||
|
||||
// Bind the descriptor sets
|
||||
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
|
||||
command_buffer.bindDescriptorSets(ToVkPipelineBindPoint(handle->GetType()), pipeline_layout.GetLayout(),
|
||||
0, descriptor_sets, {});
|
||||
}
|
||||
|
||||
} // namespace VideoCore::Vulkan
|
||||
|
@ -15,7 +15,8 @@ namespace VideoCore::Vulkan {
|
||||
|
||||
class Texture;
|
||||
|
||||
constexpr u32 RENDERPASS_COUNT = MAX_COLOR_FORMATS * MAX_DEPTH_FORMATS;
|
||||
constexpr u32 RENDERPASS_COUNT = (MAX_COLOR_FORMATS + 1) * (MAX_DEPTH_FORMATS + 1);
|
||||
constexpr u32 DESCRIPTOR_BANK_SIZE = 64;
|
||||
|
||||
class Backend : public VideoCore::BackendBase {
|
||||
public:
|
||||
@ -39,7 +40,7 @@ public:
|
||||
u32 base_vertex, u32 num_vertices) override;
|
||||
|
||||
void DrawIndexed(PipelineHandle pipeline, FramebufferHandle draw_framebuffer,
|
||||
BufferHandle vertex_buffer, BufferHandle index_buffer,
|
||||
BufferHandle vertex_buffer, BufferHandle index_buffer, AttribType index_type,
|
||||
u32 base_index, u32 num_indices, u32 base_vertex) override;
|
||||
|
||||
void DispatchCompute(PipelineHandle pipeline, Common::Vec3<u32> groupsize,
|
||||
@ -57,6 +58,10 @@ public:
|
||||
|
||||
private:
|
||||
vk::RenderPass CreateRenderPass(vk::Format color, vk::Format depth) const;
|
||||
vk::RenderPass GetRenderPass(TextureFormat color, TextureFormat depth) const;
|
||||
|
||||
// Allocates and binds descriptor sets for the provided pipeline
|
||||
void BindDescriptorSets(PipelineHandle pipeline);
|
||||
|
||||
private:
|
||||
Instance instance;
|
||||
@ -70,6 +75,9 @@ private:
|
||||
|
||||
// Pipeline layout cache
|
||||
std::unordered_map<PipelineLayoutInfo, PipelineLayout> pipeline_layouts;
|
||||
|
||||
// Descriptor pools
|
||||
std::array<vk::DescriptorPool, SCHEDULER_COMMAND_COUNT> descriptor_pools;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
97
src/video_core/renderer_vulkan/vk_framebuffer.cpp
Normal file
97
src/video_core/renderer_vulkan/vk_framebuffer.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#define VULKAN_HPP_NO_CONSTRUCTORS
|
||||
#include "video_core/renderer_vulkan/vk_framebuffer.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture.h"
|
||||
#include "video_core/renderer_vulkan/vk_task_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
|
||||
namespace VideoCore::Vulkan {
|
||||
|
||||
vk::Rect2D ToVkRect2D(Rect2D rect) {
|
||||
return vk::Rect2D{
|
||||
.offset = {rect.x, rect.y},
|
||||
.extent = {rect.width, rect.height}
|
||||
};
|
||||
}
|
||||
|
||||
Framebuffer::Framebuffer(Instance& instance, CommandScheduler& scheduler, const FramebufferInfo& info,
|
||||
vk::RenderPass load_renderpass, vk::RenderPass clear_renderpass) :
|
||||
FramebufferBase(info), instance(instance), scheduler(scheduler), load_renderpass(load_renderpass),
|
||||
clear_renderpass(clear_renderpass) {
|
||||
|
||||
const Texture* color = static_cast<const Texture*>(info.color.Get());
|
||||
const Texture* depth_stencil = static_cast<const Texture*>(info.depth_stencil.Get());
|
||||
|
||||
u32 attachment_count = 0;
|
||||
std::array<vk::ImageView, 2> attachments;
|
||||
|
||||
if (color) {
|
||||
attachments[attachment_count++] = color->GetView();
|
||||
}
|
||||
|
||||
if (depth_stencil) {
|
||||
attachments[attachment_count++] = depth_stencil->GetView();
|
||||
}
|
||||
|
||||
const Texture* valid_texture = color ? color : depth_stencil;
|
||||
const vk::FramebufferCreateInfo framebuffer_info = {
|
||||
// The load and clear renderpass are compatible according to the specification
|
||||
// so there is no need to create multiple framebuffers
|
||||
.renderPass = load_renderpass,
|
||||
.attachmentCount = attachments.size(),
|
||||
.pAttachments = attachments.data(),
|
||||
.width = valid_texture->GetWidth(),
|
||||
.height = valid_texture->GetHeight(),
|
||||
.layers = 1
|
||||
};
|
||||
|
||||
vk::Device device = instance.GetDevice();
|
||||
framebuffer = device.createFramebuffer(framebuffer_info);
|
||||
}
|
||||
|
||||
Framebuffer::~Framebuffer() {
|
||||
vk::Device device = instance.GetDevice();
|
||||
device.destroyFramebuffer(framebuffer);
|
||||
}
|
||||
|
||||
void Framebuffer::DoClear(Common::Rectangle<u32> rect, Common::Vec4f color, float depth, u8 stencil) {
|
||||
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
|
||||
|
||||
u32 clear_value_count = 0;
|
||||
std::array<vk::ClearValue, 2> clear_values{};
|
||||
|
||||
if (info.color.IsValid()) {
|
||||
vk::ClearColorValue clear_color{};
|
||||
std::memcpy(clear_color.float32.data(), color.AsArray(), sizeof(float) * 4);
|
||||
|
||||
clear_values[clear_value_count++] = vk::ClearValue {
|
||||
.color = clear_color
|
||||
};
|
||||
}
|
||||
|
||||
if (info.depth_stencil.IsValid()) {
|
||||
clear_values[clear_value_count++] = vk::ClearValue {
|
||||
.depthStencil = vk::ClearDepthStencilValue {
|
||||
.depth = depth,
|
||||
.stencil = stencil
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const vk::RenderPassBeginInfo begin_info = {
|
||||
.renderPass = clear_renderpass,
|
||||
.framebuffer = framebuffer,
|
||||
.renderArea = ToVkRect2D(rect),
|
||||
.clearValueCount = clear_value_count,
|
||||
.pClearValues = clear_values.data()
|
||||
};
|
||||
|
||||
// Begin clear pass
|
||||
command_buffer.beginRenderPass(begin_info, vk::SubpassContents::eInline);
|
||||
command_buffer.endRenderPass();
|
||||
}
|
||||
|
||||
} // namespace VideoCore::Vulkan
|
38
src/video_core/renderer_vulkan/vk_framebuffer.h
Normal file
38
src/video_core/renderer_vulkan/vk_framebuffer.h
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "video_core/common/framebuffer.h"
|
||||
#include "video_core/renderer_vulkan/vk_common.h"
|
||||
|
||||
namespace VideoCore::Vulkan {
|
||||
|
||||
class Instance;
|
||||
class CommandScheduler;
|
||||
|
||||
class Framebuffer : public VideoCore::FramebufferBase {
|
||||
public:
|
||||
Framebuffer(Instance& instance, CommandScheduler& scheduler, const FramebufferInfo& info,
|
||||
vk::RenderPass load_renderpass, vk::RenderPass clear_renderpass);
|
||||
~Framebuffer() override;
|
||||
|
||||
void DoClear(Common::Rectangle<u32> rect, Common::Vec4f color, float depth, u8 stencil) override;
|
||||
|
||||
vk::Framebuffer GetHandle() const {
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
vk::RenderPass GetLoadRenderpass() const {
|
||||
return load_renderpass;
|
||||
}
|
||||
|
||||
private:
|
||||
Instance& instance;
|
||||
CommandScheduler& scheduler;
|
||||
vk::Framebuffer framebuffer;
|
||||
vk::RenderPass load_renderpass, clear_renderpass;
|
||||
};
|
||||
|
||||
} // namespace VideoCore::Vulkan
|
@ -183,8 +183,8 @@ PipelineLayout::~PipelineLayout() {
|
||||
}
|
||||
}
|
||||
|
||||
Pipeline::Pipeline(Instance& instance, PipelineLayout& owner, PipelineType type,
|
||||
PipelineInfo info, vk::PipelineCache cache) : PipelineBase(type, info),
|
||||
Pipeline::Pipeline(Instance& instance, PipelineLayout& owner, PipelineType type, PipelineInfo info,
|
||||
vk::RenderPass renderpass, vk::PipelineCache cache) : PipelineBase(type, info),
|
||||
instance(instance), owner(owner) {
|
||||
|
||||
vk::Device device = instance.GetDevice();
|
||||
@ -331,7 +331,7 @@ Pipeline::Pipeline(Instance& instance, PipelineLayout& owner, PipelineType type,
|
||||
.pColorBlendState = &color_blending,
|
||||
.pDynamicState = &dynamic_info,
|
||||
.layout = owner.GetLayout(),
|
||||
.renderPass = {}
|
||||
.renderPass = renderpass
|
||||
};
|
||||
|
||||
if (auto result = device.createGraphicsPipeline(cache, pipeline_info); result.result == vk::Result::eSuccess) {
|
||||
|
@ -38,11 +38,20 @@ public:
|
||||
update_data[set][binding] = data;
|
||||
}
|
||||
|
||||
// Returns the number of descriptor set layouts
|
||||
u32 GetDescriptorSetLayoutCount() const {
|
||||
return set_layout_count;
|
||||
}
|
||||
|
||||
// Returns the most current descriptor update data
|
||||
std::span<DescriptorData> GetData(u32 set) {
|
||||
return std::span{update_data.at(set).data(), set_layout_count};
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout* GetDescriptorSetLayouts() {
|
||||
return set_layouts.data();
|
||||
}
|
||||
|
||||
// Returns the underlying vulkan pipeline layout handle
|
||||
vk::PipelineLayout GetLayout() const {
|
||||
return pipeline_layout;
|
||||
@ -67,8 +76,8 @@ private:
|
||||
|
||||
class Pipeline : public VideoCore::PipelineBase {
|
||||
public:
|
||||
Pipeline(Instance& instance, PipelineLayout& owner,
|
||||
PipelineType type, PipelineInfo info, vk::PipelineCache cache);
|
||||
Pipeline(Instance& instance, PipelineLayout& owner, PipelineType type, PipelineInfo info,
|
||||
vk::RenderPass renderpass, vk::PipelineCache cache);
|
||||
~Pipeline() override;
|
||||
|
||||
void BindTexture(u32 group, u32 slot, TextureHandle handle) override;
|
||||
|
@ -70,8 +70,9 @@ private:
|
||||
struct CommandSlot {
|
||||
bool use_upload_buffer = false;
|
||||
u64 fence_counter = 0;
|
||||
vk::CommandBuffer render_command_buffer, upload_command_buffer;
|
||||
vk::Fence fence = VK_NULL_HANDLE;
|
||||
vk::CommandBuffer render_command_buffer;
|
||||
vk::CommandBuffer upload_command_buffer;
|
||||
std::unique_ptr<Buffer> upload_buffer;
|
||||
std::vector<Deleter> cleanups;
|
||||
};
|
||||
|
@ -76,8 +76,7 @@ inline vk::ImageViewType ToVkImageViewType(TextureViewType view_type) {
|
||||
Texture::Texture(Instance& instance, CommandScheduler& scheduler) :
|
||||
instance(instance), scheduler(scheduler) {}
|
||||
|
||||
Texture::Texture(Instance& instance, CommandScheduler& scheduler,
|
||||
const TextureInfo& info) : TextureBase(info),
|
||||
Texture::Texture(Instance& instance, CommandScheduler& scheduler, const TextureInfo& info) : TextureBase(info),
|
||||
instance(instance), scheduler(scheduler) {
|
||||
|
||||
// Convert the input format to another that supports attachments
|
||||
@ -122,10 +121,8 @@ Texture::Texture(Instance& instance, CommandScheduler& scheduler,
|
||||
image_view = device.createImageView(view_info);
|
||||
}
|
||||
|
||||
Texture::Texture(Instance& instance, CommandScheduler& scheduler,
|
||||
vk::Image image, const TextureInfo& info) : TextureBase(info),
|
||||
instance(instance), scheduler(scheduler), image(image),
|
||||
is_texture_owned(false) {
|
||||
Texture::Texture(Instance& instance, CommandScheduler& scheduler, vk::Image image, const TextureInfo& info) :
|
||||
TextureBase(info), instance(instance), scheduler(scheduler), image(image), is_texture_owned(false) {
|
||||
|
||||
const vk::ImageViewCreateInfo view_info = {
|
||||
.image = image,
|
||||
@ -157,8 +154,7 @@ Texture::~Texture() {
|
||||
}
|
||||
}
|
||||
|
||||
void Texture::Transition(vk::CommandBuffer command_buffer, vk::ImageLayout new_layout,
|
||||
u32 level, u32 level_count) {
|
||||
void Texture::Transition(vk::CommandBuffer command_buffer, vk::ImageLayout new_layout, u32 level, u32 level_count) {
|
||||
ASSERT(level + level_count < TEXTURE_MAX_LEVELS);
|
||||
|
||||
// Ensure all miplevels in the range have the same layout
|
||||
@ -411,8 +407,101 @@ void Texture::Download(Rect2D rectangle, u32 stride, std::span<u8> data, u32 lev
|
||||
}
|
||||
}
|
||||
|
||||
StagingTexture::StagingTexture(Instance& instance, CommandScheduler& scheduler,
|
||||
const TextureInfo& info) :
|
||||
void Texture::BlitTo(TextureHandle dest, Rect2D source_rect, Rect2D dest_rect, u32 src_level, u32 dest_level,
|
||||
u32 src_layer, u32 dest_layer) {
|
||||
Texture* dest_texture = static_cast<Texture*>(dest.Get());
|
||||
|
||||
// Prepare images for transfer
|
||||
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
|
||||
Transition(command_buffer, vk::ImageLayout::eTransferSrcOptimal);
|
||||
dest_texture->Transition(command_buffer, vk::ImageLayout::eTransferDstOptimal);
|
||||
|
||||
const std::array source_offsets = {
|
||||
vk::Offset3D{source_rect.x, source_rect.y, 0},
|
||||
vk::Offset3D{static_cast<s32>(source_rect.x + source_rect.width),
|
||||
static_cast<s32>(source_rect.y + source_rect.height), 1}
|
||||
};
|
||||
|
||||
const std::array dest_offsets = {
|
||||
vk::Offset3D{dest_rect.x, dest_rect.y, 0},
|
||||
vk::Offset3D{static_cast<s32>(dest_rect.x + dest_rect.width),
|
||||
static_cast<s32>(dest_rect.y + dest_rect.height), 1}
|
||||
};
|
||||
|
||||
const vk::ImageBlit blit_area = {
|
||||
.srcSubresource = {
|
||||
.aspectMask = aspect,
|
||||
.mipLevel = src_level,
|
||||
.baseArrayLayer = src_layer,
|
||||
.layerCount = 1
|
||||
},
|
||||
.srcOffsets = source_offsets,
|
||||
.dstSubresource = {
|
||||
.aspectMask = dest_texture->GetAspectFlags(),
|
||||
.mipLevel = dest_level,
|
||||
.baseArrayLayer = dest_layer,
|
||||
.layerCount = 1
|
||||
},
|
||||
.dstOffsets = dest_offsets
|
||||
};
|
||||
|
||||
command_buffer.blitImage(image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
dest_texture->GetHandle(), vk::ImageLayout::eTransferDstOptimal,
|
||||
blit_area, vk::Filter::eNearest);
|
||||
|
||||
// Revert changes to the layout
|
||||
Transition(command_buffer, vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
dest_texture->Transition(command_buffer, vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
}
|
||||
|
||||
// TODO: Use AMD single pass downsampler
|
||||
void Texture::GenerateMipmaps() {
|
||||
s32 current_width = info.width;
|
||||
s32 current_height = info.height;
|
||||
|
||||
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
|
||||
for (u32 i = 1; i < info.levels; i++) {
|
||||
Transition(command_buffer, vk::ImageLayout::eTransferSrcOptimal, i - 1);
|
||||
Transition(command_buffer, vk::ImageLayout::eTransferDstOptimal, i);
|
||||
|
||||
const std::array source_offsets = {
|
||||
vk::Offset3D{0, 0, 0},
|
||||
vk::Offset3D{current_width, current_height, 1}
|
||||
};
|
||||
|
||||
const std::array dest_offsets = {
|
||||
vk::Offset3D{0, 0, 0},
|
||||
vk::Offset3D{current_width > 1 ? current_width / 2 : 1,
|
||||
current_height > 1 ? current_height / 2 : 1, 1}
|
||||
};
|
||||
|
||||
const vk::ImageBlit blit_area = {
|
||||
.srcSubresource = {
|
||||
.aspectMask = aspect,
|
||||
.mipLevel = i - 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
},
|
||||
.srcOffsets = source_offsets,
|
||||
.dstSubresource = {
|
||||
.aspectMask = aspect,
|
||||
.mipLevel = i,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
},
|
||||
.dstOffsets = dest_offsets
|
||||
};
|
||||
|
||||
command_buffer.blitImage(image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
image, vk::ImageLayout::eTransferDstOptimal,
|
||||
blit_area, vk::Filter::eLinear);
|
||||
}
|
||||
|
||||
// Prepare for shader reads
|
||||
Transition(command_buffer, vk::ImageLayout::eShaderReadOnlyOptimal, 0, info.levels);
|
||||
}
|
||||
|
||||
StagingTexture::StagingTexture(Instance& instance, CommandScheduler& scheduler, const TextureInfo& info) :
|
||||
TextureBase(info), instance(instance), scheduler(scheduler) {
|
||||
|
||||
format = ToVkFormat(info.format);
|
||||
|
@ -24,33 +24,28 @@ public:
|
||||
Texture(Instance& instance, CommandScheduler& scheduler);
|
||||
|
||||
// Constructor for texture creation
|
||||
Texture(Instance& instance, CommandScheduler& scheduler,
|
||||
const TextureInfo& info);
|
||||
Texture(Instance& instance, CommandScheduler& scheduler, const TextureInfo& info);
|
||||
|
||||
// Constructor for not owning textures (swapchain)
|
||||
Texture(Instance& instance, CommandScheduler& scheduler,
|
||||
vk::Image image, const TextureInfo& info);
|
||||
Texture(Instance& instance, CommandScheduler& scheduler, vk::Image image, const TextureInfo& info);
|
||||
|
||||
~Texture();
|
||||
~Texture() override;
|
||||
|
||||
/// Uploads pixel data to the GPU memory
|
||||
void Upload(Rect2D rectangle, u32 stride, std::span<const u8> data,
|
||||
u32 level = 0) override;
|
||||
void Upload(Rect2D rectangle, u32 stride, std::span<const u8> data, u32 level = 0) override;
|
||||
|
||||
/// Downloads pixel data from GPU memory
|
||||
void Download(Rect2D rectangle, u32 stride, std::span<u8> data,
|
||||
u32 level = 0) override;
|
||||
void Download(Rect2D rectangle, u32 stride, std::span<u8> data, u32 level = 0) override;
|
||||
|
||||
/// Copies the rectangle area specified to the destionation texture
|
||||
void BlitTo(TextureHandle dest, Rect2D src_rectangle, Rect2D dest_rect,
|
||||
u32 src_level = 0, u32 dest_level = 0) override;
|
||||
void BlitTo(TextureHandle dest, Rect2D src_rectangle, Rect2D dest_rect, u32 src_level = 0,
|
||||
u32 dest_level = 0, u32 src_layer = 0, u32 dest_layer = 0) override;
|
||||
|
||||
void GenerateMipmaps() override;
|
||||
|
||||
/// Overrides the layout of provided image subresource
|
||||
void SetLayout(vk::ImageLayout new_layout, u32 level = 0, u32 level_count = 1);
|
||||
|
||||
/// Transitions part of the image to the provided layout
|
||||
void Transition(vk::CommandBuffer command_buffer, vk::ImageLayout new_layout,
|
||||
u32 level = 0, u32 level_count = 1);
|
||||
void Transition(vk::CommandBuffer command_buffer, vk::ImageLayout new_layout, u32 level = 0,
|
||||
u32 level_count = 1);
|
||||
|
||||
/// Returns the underlying vulkan image handle
|
||||
vk::Image GetHandle() const {
|
||||
@ -68,6 +63,10 @@ public:
|
||||
return internal_format;
|
||||
}
|
||||
|
||||
vk::ImageAspectFlags GetAspectFlags() const {
|
||||
return aspect;
|
||||
}
|
||||
|
||||
/// Returns the current image layout
|
||||
vk::ImageLayout GetLayout(u32 level = 0) const {
|
||||
return layouts.at(level);
|
||||
|
Reference in New Issue
Block a user