video_core: Commonise rasterizer cache

This commit is contained in:
emufan4568
2022-08-08 14:24:47 +03:00
parent 810df95b81
commit 2d4f762db1
22 changed files with 2997 additions and 97 deletions

View File

@ -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;
};

View File

@ -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

View File

@ -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

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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);

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View File

@ -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{};
};

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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

View 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

View 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

View File

@ -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) {

View File

@ -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;

View File

@ -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;
};

View File

@ -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);

View File

@ -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);