rasterizer_cache: Support multi-level surfaces

* With this commit the cache can now directly upload and use mipmaps
  without needing to sync them with watchers. By using native mimaps
  directly this also adds support for mipmap for cube

* Since watchers have been removed texture cubes still work but are uncached
  so slower as well. Will be fixed soon.
This commit is contained in:
GPUCode
2023-02-14 22:27:50 +02:00
parent 087bcd8f97
commit df6b36eb67
13 changed files with 235 additions and 290 deletions

View File

@ -64,6 +64,8 @@ bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format) {
return true;
}
LOG_WARNING(HW_GPU, "Unblittable format pair detected {} and {}",
PixelFormatAsString(source_format), PixelFormatAsString(dest_format));
return false;
}

View File

@ -122,14 +122,13 @@ bool RasterizerCache<T>::AccelerateTextureCopy(const GPU::Regs::DisplayTransferC
ASSERT(src_rect.GetWidth() == dst_rect.GetWidth());
const TextureCopy texture_copy = {
.src_level = 0,
.dst_level = 0,
.src_level = src_surface->LevelOf(src_params.addr),
.dst_level = dst_surface->LevelOf(dst_params.addr),
.src_offset = {src_rect.left, src_rect.bottom},
.dst_offset = {dst_rect.left, dst_rect.bottom},
.extent = {src_rect.GetWidth(), src_rect.GetHeight()},
};
runtime.CopyTextures(*src_surface, *dst_surface, texture_copy);
dst_surface->InvalidateAllWatcher();
InvalidateRegion(dst_params.addr, dst_params.size, dst_surface);
return true;
@ -179,13 +178,12 @@ bool RasterizerCache<T>::AccelerateDisplayTransfer(const GPU::Regs::DisplayTrans
}
const TextureBlit texture_blit = {
.src_level = 0,
.dst_level = 0,
.src_level = src_surface->LevelOf(src_params.addr),
.dst_level = dst_surface->LevelOf(dst_params.addr),
.src_rect = src_rect,
.dst_rect = dst_rect,
};
runtime.BlitTextures(*src_surface, *dst_surface, texture_blit);
dst_surface->InvalidateAllWatcher();
InvalidateRegion(dst_params.addr, dst_params.size, dst_surface);
return true;
@ -374,14 +372,14 @@ void RasterizerCache<T>::CopySurface(const Surface& src_surface, const Surface&
SurfaceInterval copy_interval) {
MICROPROFILE_SCOPE(RasterizerCache_CopySurface);
const PAddr copy_addr = copy_interval.lower();
const auto subrect_params = dst_surface->FromInterval(copy_interval);
const Rect2D dst_rect = dst_surface->GetScaledSubRect(subrect_params);
const PAddr copy_addr = copy_interval.lower();
ASSERT(subrect_params.GetInterval() == copy_interval && src_surface != dst_surface);
if (src_surface->type == SurfaceType::Fill) {
const TextureClear texture_clear = {
.texture_level = 0,
.texture_level = dst_surface->LevelOf(copy_addr),
.texture_rect = dst_rect,
.value = src_surface->MakeClearValue(copy_addr, dst_surface->pixel_format),
};
@ -390,10 +388,8 @@ void RasterizerCache<T>::CopySurface(const Surface& src_surface, const Surface&
}
const TextureBlit texture_blit = {
.src_level = 0,
.dst_level = 0,
.src_layer = 0,
.dst_layer = 0,
.src_level = src_surface->LevelOf(copy_addr),
.dst_level = dst_surface->LevelOf(copy_addr),
.src_rect = src_surface->GetScaledSubRect(subrect_params),
.dst_rect = dst_rect,
};
@ -494,6 +490,7 @@ auto RasterizerCache<T>::GetSurfaceSubRect(const SurfaceParams& params, ScaleMat
new_params.size = new_params.end - new_params.addr;
new_params.height =
new_params.size / aligned_params.BytesInPixels(aligned_params.stride);
new_params.UpdateParams();
ASSERT(new_params.size % aligned_params.BytesInPixels(aligned_params.stride) == 0);
Surface new_surface = CreateSurface(new_params);
@ -501,7 +498,6 @@ auto RasterizerCache<T>::GetSurfaceSubRect(const SurfaceParams& params, ScaleMat
// Delete the expanded surface, this can't be done safely yet
// because it may still be in use
surface->UnlinkAllWatcher(); // unlink watchers as if this surface is already deleted
remove_surfaces.push_back(surface);
surface = new_surface;
@ -561,64 +557,7 @@ auto RasterizerCache<T>::GetTextureSurface(const Pica::Texture::TextureInfo& inf
return nullptr;
}
auto surface = GetSurface(params, ScaleMatch::Ignore, true);
if (!surface) {
return nullptr;
}
// Update mipmap if necessary
if (max_level != 0) {
if (max_level >= 8) {
// Since PICA only supports texture size between 8 and 1024, there are at most eight
// possible mipmap levels including the base.
LOG_CRITICAL(HW_GPU, "Unsupported mipmap level {}", max_level);
return nullptr;
}
// Blit mipmaps that have been invalidated
SurfaceParams surface_params = *surface;
for (u32 level = 1; level <= max_level; level++) {
// In PICA all mipmap levels are stored next to each other
surface_params.addr +=
surface_params.width * surface_params.height * surface_params.GetFormatBpp() / 8;
surface_params.width /= 2;
surface_params.height /= 2;
surface_params.stride = 0; // reset stride and let UpdateParams re-initialize it
surface_params.levels = 1;
surface_params.UpdateParams();
auto& watcher = surface->level_watchers[level - 1];
if (!watcher || !watcher->Get()) {
auto level_surface = GetSurface(surface_params, ScaleMatch::Ignore, true);
if (level_surface) {
watcher = level_surface->CreateWatcher();
} else {
watcher = nullptr;
}
}
if (watcher && !watcher->IsValid()) {
auto level_surface =
std::static_pointer_cast<typename T::SurfaceType>(watcher->Get());
if (!level_surface->invalid_regions.empty()) {
ValidateSurface(level_surface, level_surface->addr, level_surface->size);
}
const TextureBlit texture_blit = {
.src_level = 0,
.dst_level = level,
.src_layer = 0,
.dst_layer = 0,
.src_rect = level_surface->GetScaledRect(),
.dst_rect = surface_params.GetScaledRect(),
};
runtime.BlitTextures(*level_surface, *surface, texture_blit);
watcher->Validate();
}
}
}
return surface;
return GetSurface(params, ScaleMatch::Ignore, true);
}
template <class T>
@ -630,6 +569,7 @@ auto RasterizerCache<T>::GetTextureCube(const TextureCubeConfig& config) -> cons
.width = config.width,
.height = config.width,
.stride = config.width,
.levels = config.levels,
.texture_type = TextureType::CubeMap,
.pixel_format = PixelFormatFromTextureFormat(config.format),
.type = SurfaceType::Texture,
@ -640,53 +580,36 @@ auto RasterizerCache<T>::GetTextureCube(const TextureCubeConfig& config) -> cons
Surface& cube = it->second;
// Update surface watchers
auto& watchers = cube->level_watchers;
const u32 scaled_size = cube->GetScaledWidth();
const std::array addresses = {config.px, config.nx, config.py, config.ny, config.pz, config.nz};
for (std::size_t i = 0; i < addresses.size(); i++) {
auto& watcher = watchers[i];
if (!watcher || !watcher->Get()) {
Pica::Texture::TextureInfo info = {
.physical_address = addresses[i],
.width = config.width,
.height = config.width,
.format = config.format,
};
Pica::Texture::TextureInfo info = {
.physical_address = addresses[i],
.width = config.width,
.height = config.width,
.format = config.format,
};
info.SetDefaultStride();
info.SetDefaultStride();
auto surface = GetTextureSurface(info);
if (surface) {
watcher = surface->CreateWatcher();
} else {
// Can occur when texture address is invalid. We mark the watcher with nullptr
// in this case and the content of the face wouldn't get updated. These are usually
// leftover setup in the texture unit and games are not supposed to draw using them.
watcher = nullptr;
}
Surface face_surface = GetTextureSurface(info, config.levels - 1);
if (!face_surface) {
continue;
}
}
// Validate the face surfaces
const u32 scaled_size = cube->GetScaledWidth();
for (std::size_t i = 0; i < addresses.size(); i++) {
const auto& watcher = watchers[i];
if (watcher && !watcher->IsValid()) {
auto face = std::static_pointer_cast<typename T::SurfaceType>(watcher->Get());
if (!face->invalid_regions.empty()) {
ValidateSurface(face, face->addr, face->size);
}
const TextureBlit texture_blit = {
.src_level = 0,
.dst_level = 0,
ASSERT(face_surface->levels == config.levels);
const u32 face = static_cast<u32>(i);
for (u32 level = 0; level < face_surface->levels; level++) {
const TextureCopy texture_copy = {
.src_level = level,
.dst_level = level,
.src_layer = 0,
.dst_layer = static_cast<u32>(i),
.src_rect = face->GetScaledRect(),
.dst_rect = Rect2D{0, scaled_size, scaled_size, 0},
.dst_layer = face,
.src_offset = {0, 0},
.dst_offset = {0, 0},
.extent = {scaled_size >> level, scaled_size >> level},
};
runtime.BlitTextures(*face, *cube, texture_blit);
watcher->Validate();
runtime.CopyTextures(*face_surface, *cube, texture_copy);
}
}
@ -773,14 +696,16 @@ auto RasterizerCache<T>::GetFramebufferSurfaces(bool using_color_fb, bool using_
}
if (color_surface) {
ASSERT_MSG(color_surface->LevelOf(color_params.addr) == 0,
"Rendering to mipmap of color surface unsupported");
ValidateSurface(color_surface, boost::icl::first(color_vp_interval),
boost::icl::length(color_vp_interval));
color_surface->InvalidateAllWatcher();
}
if (depth_surface) {
ASSERT_MSG(depth_surface->LevelOf(depth_params.addr) == 0,
"Rendering to mipmap of depth surface unsupported");
ValidateSurface(depth_surface, boost::icl::first(depth_vp_interval),
boost::icl::length(depth_vp_interval));
depth_surface->InvalidateAllWatcher();
}
render_targets = RenderTargets{
@ -874,63 +799,61 @@ void RasterizerCache<T>::ValidateSurface(const Surface& surface, PAddr addr, u32
}
const SurfaceInterval validate_interval(addr, addr + size);
const SurfaceRegions validate_regions = surface->invalid_regions & validate_interval;
if (validate_regions.empty()) {
return;
}
// Fill surfaces must always be valid when used
if (surface->type == SurfaceType::Fill) {
// Sanity check, fill surfaces will always be valid when used
ASSERT(surface->IsRegionValid(validate_interval));
return;
}
auto validate_regions = surface->invalid_regions & validate_interval;
for (u32 level = surface->LevelOf(addr); level <= surface->LevelOf(addr + size); level++) {
auto level_regions = validate_regions & surface->LevelInterval(level);
while (!level_regions.empty()) {
const SurfaceInterval interval = *level_regions.begin();
const SurfaceParams params = surface->FromInterval(interval);
const auto NotifyValidated = [&](SurfaceInterval interval) {
surface->invalid_regions.erase(interval);
validate_regions.erase(interval);
};
while (true) {
const auto it = validate_regions.begin();
if (it == validate_regions.end()) {
break;
}
// Look for a valid surface to copy from
const auto interval = *it & validate_interval;
SurfaceParams params = surface->FromInterval(interval);
Surface copy_surface = FindMatch<MatchFlags::Copy>(params, ScaleMatch::Ignore, interval);
if (copy_surface != nullptr) {
SurfaceInterval copy_interval = copy_surface->GetCopyableInterval(params);
CopySurface(copy_surface, surface, copy_interval);
NotifyValidated(copy_interval);
continue;
}
// Try to find surface in cache with different format
// that can can be reinterpreted to the requested format.
if (ValidateByReinterpretation(surface, params, interval)) {
NotifyValidated(interval);
continue;
}
// Could not find a matching reinterpreter, check if we need to implement a
// reinterpreter
if (NoUnimplementedReinterpretations(surface, params, interval) &&
!IntervalHasInvalidPixelFormat(params, interval)) {
// No surfaces were found in the cache that had a matching bit-width.
// If the region was created entirely on the GPU,
// assume it was a developer mistake and skip flushing.
if (boost::icl::contains(dirty_regions, interval)) {
LOG_DEBUG(HW_GPU, "Region created fully on GPU and reinterpretation is "
"invalid. Skipping validation");
validate_regions.erase(interval);
Surface copy_surface =
FindMatch<MatchFlags::Copy>(params, ScaleMatch::Ignore, interval);
if (copy_surface) {
const SurfaceInterval copy_interval = copy_surface->GetCopyableInterval(params);
CopySurface(copy_surface, surface, copy_interval);
level_regions.erase(copy_interval);
continue;
}
}
// Load data from 3DS memory
FlushRegion(params.addr, params.size);
UploadSurface(surface, interval);
NotifyValidated(params.GetInterval());
// Try to find surface in cache with different format
// that can can be reinterpreted to the requested format.
if (ValidateByReinterpretation(surface, params, interval)) {
level_regions.erase(interval);
continue;
}
// Could not find a matching reinterpreter, check if we need to implement a
// reinterpreter
if (NoUnimplementedReinterpretations(surface, params, interval) &&
!IntervalHasInvalidPixelFormat(params, interval)) {
// No surfaces were found in the cache that had a matching bit-width.
// If the region was created entirely on the GPU,
// assume it was a developer mistake and skip flushing.
if (boost::icl::contains(dirty_regions, interval)) {
LOG_DEBUG(HW_GPU, "Region created fully on GPU and reinterpretation is "
"invalid. Skipping validation");
level_regions.erase(interval);
continue;
}
}
// Load data from 3DS memory
FlushRegion(params.addr, params.size);
UploadSurface(surface, interval);
level_regions.erase(params.GetInterval());
}
}
surface->invalid_regions.erase(validate_interval);
}
template <class T>
@ -956,7 +879,7 @@ void RasterizerCache<T>::UploadSurface(const Surface& surface, SurfaceInterval i
.buffer_offset = 0,
.buffer_size = staging.size,
.texture_rect = surface->GetSubRect(load_info),
.texture_level = 0,
.texture_level = surface->LevelOf(load_info.addr),
};
surface->Upload(upload, staging);
}
@ -975,7 +898,7 @@ void RasterizerCache<T>::DownloadSurface(const Surface& surface, SurfaceInterval
.buffer_offset = 0,
.buffer_size = staging.size,
.texture_rect = surface->GetSubRect(flush_info),
.texture_level = 0,
.texture_level = surface->LevelOf(flush_start),
};
surface->Download(download, staging);
@ -1025,7 +948,7 @@ void RasterizerCache<T>::DownloadFillSurface(const Surface& surface, SurfaceInte
template <class T>
bool RasterizerCache<T>::NoUnimplementedReinterpretations(const Surface& surface,
SurfaceParams& params,
SurfaceParams params,
SurfaceInterval interval) {
static constexpr std::array all_formats = {
PixelFormat::RGBA8, PixelFormat::RGB8, PixelFormat::RGB5A1, PixelFormat::RGB565,
@ -1056,7 +979,7 @@ bool RasterizerCache<T>::NoUnimplementedReinterpretations(const Surface& surface
}
template <class T>
bool RasterizerCache<T>::IntervalHasInvalidPixelFormat(SurfaceParams& params,
bool RasterizerCache<T>::IntervalHasInvalidPixelFormat(SurfaceParams params,
SurfaceInterval interval) {
bool invalid_format_found = false;
ForEachSurfaceInRegion(params.addr, params.end, [&](Surface surface) {
@ -1072,7 +995,7 @@ bool RasterizerCache<T>::IntervalHasInvalidPixelFormat(SurfaceParams& params,
}
template <class T>
bool RasterizerCache<T>::ValidateByReinterpretation(const Surface& surface, SurfaceParams& params,
bool RasterizerCache<T>::ValidateByReinterpretation(const Surface& surface, SurfaceParams params,
SurfaceInterval interval) {
const PixelFormat dest_format = surface->pixel_format;
for (const auto& reinterpreter : runtime.GetPossibleReinterpretations(dest_format)) {

View File

@ -157,14 +157,14 @@ private:
void DownloadFillSurface(const Surface& surface, SurfaceInterval interval);
/// 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,
bool NoUnimplementedReinterpretations(const Surface& surface, SurfaceParams params,
SurfaceInterval interval);
/// Return true if a surface with an invalid pixel format exists at the interval
bool IntervalHasInvalidPixelFormat(SurfaceParams& params, SurfaceInterval interval);
bool IntervalHasInvalidPixelFormat(SurfaceParams params, 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,
bool ValidateByReinterpretation(const Surface& surface, SurfaceParams params,
SurfaceInterval interval);
/// Create a new surface

View File

@ -151,30 +151,4 @@ std::array<u8, 4> SurfaceBase::MakeFillBuffer(PAddr copy_addr) {
return fill_buffer;
}
std::shared_ptr<Watcher> SurfaceBase::CreateWatcher() {
auto weak_ptr = weak_from_this();
auto watcher = std::make_shared<Watcher>(std::move(weak_ptr));
watchers.push_back(watcher);
return watcher;
}
void SurfaceBase::InvalidateAllWatcher() {
for (const auto& watcher : watchers) {
if (auto locked = watcher.lock()) {
locked->valid = false;
}
}
}
void SurfaceBase::UnlinkAllWatcher() {
for (const auto& watcher : watchers) {
if (auto locked = watcher.lock()) {
locked->valid = false;
locked->surface.reset();
}
}
watchers.clear();
}
} // namespace VideoCore

View File

@ -12,38 +12,7 @@ namespace VideoCore {
using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>;
class SurfaceBase;
/**
* A watcher that notifies whether a cached surface has been changed. This is useful for caching
* surface collection objects, including texture cube and mipmap.
*/
class Watcher {
public:
explicit Watcher(std::weak_ptr<SurfaceBase>&& 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
std::shared_ptr<SurfaceBase> Get() const {
return surface.lock();
}
public:
std::weak_ptr<SurfaceBase> surface;
bool valid = false;
};
class SurfaceBase : public SurfaceParams, public std::enable_shared_from_this<SurfaceBase> {
class SurfaceBase : public SurfaceParams {
public:
SurfaceBase();
explicit SurfaceBase(const SurfaceParams& params);
@ -66,15 +35,6 @@ public:
/// Returns the clear value used to validate another surface from this fill surface
ClearValue MakeClearValue(PAddr copy_addr, PixelFormat dst_format);
/// Creates a surface watcher linked to this surface
std::shared_ptr<Watcher> CreateWatcher();
/// Invalidates all watchers linked to this surface
void InvalidateAllWatcher();
/// Removes any linked watchers from this surface
void UnlinkAllWatcher();
/// Returns true when the region denoted by interval is valid
bool IsRegionValid(SurfaceInterval interval) const {
return (invalid_regions.find(interval) == invalid_regions.end());
@ -94,12 +54,8 @@ public:
bool registered = false;
bool picked = false;
SurfaceRegions invalid_regions;
std::array<std::shared_ptr<Watcher>, 7> level_watchers;
std::array<u8, 4> fill_data;
u32 fill_size = 0;
public:
std::vector<std::weak_ptr<Watcher>> watchers;
};
} // namespace VideoCore

View File

@ -56,24 +56,37 @@ void SurfaceParams::UpdateParams() {
}
type = GetFormatType(pixel_format);
size = !is_tiled ? BytesInPixels(stride * (height - 1) + width)
: BytesInPixels(stride * 8 * (height / 8 - 1) + width * 8);
if (levels != 1) {
ASSERT(stride == width);
CalculateMipLevelOffsets();
size = CalculateSurfaceSize();
} else {
mipmap_offsets[0] = addr;
size = !is_tiled ? BytesInPixels(stride * (height - 1) + width)
: BytesInPixels(stride * 8 * (height / 8 - 1) + width * 8);
}
end = addr + size;
}
Rect2D SurfaceParams::GetSubRect(const SurfaceParams& sub_surface) const {
const u32 begin_pixel_index = PixelsInBytes(sub_surface.addr - addr);
const u32 level = LevelOf(sub_surface.addr);
const u32 begin_pixel_index = PixelsInBytes(sub_surface.addr - mipmap_offsets[level]);
ASSERT(stride == width || level == 0);
const u32 stride_lod = stride >> level;
if (is_tiled) {
const u32 x0 = (begin_pixel_index % (stride * 8)) / 8;
const u32 y0 = (begin_pixel_index / (stride * 8)) * 8;
const u32 x0 = (begin_pixel_index % (stride_lod * 8)) / 8;
const u32 y0 = (begin_pixel_index / (stride_lod * 8)) * 8;
const u32 height_lod = height >> level;
// Top to bottom
return Rect2D(x0, height - y0, x0 + sub_surface.width, height - (y0 + sub_surface.height));
return Rect2D(x0, height_lod - y0, x0 + sub_surface.width,
height_lod - (y0 + sub_surface.height));
}
const u32 x0 = begin_pixel_index % stride;
const u32 y0 = begin_pixel_index / stride;
const u32 x0 = begin_pixel_index % stride_lod;
const u32 y0 = begin_pixel_index / stride_lod;
// Bottom to top
return Rect2D(x0, y0 + sub_surface.height, x0 + sub_surface.width, y0);
}
@ -85,26 +98,37 @@ Rect2D SurfaceParams::GetScaledSubRect(const SurfaceParams& sub_surface) const {
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);
const u32 level = LevelOf(interval.lower());
const PAddr end_addr = interval.upper();
// Ensure provided interval is contained in a single level
ASSERT(level == LevelOf(end_addr) || end_addr == end || end_addr == mipmap_offsets[level + 1]);
params.width >>= level;
params.stride >>= level;
const u32 tiled_size = is_tiled ? 8 : 1;
const u32 stride_tiled_bytes = BytesInPixels(params.stride * tiled_size);
ASSERT(stride == width || level == 0);
const PAddr start = mipmap_offsets[level];
PAddr aligned_start =
addr + Common::AlignDown(boost::icl::first(interval) - addr, stride_tiled_bytes);
start + Common::AlignDown(boost::icl::first(interval) - start, stride_tiled_bytes);
PAddr aligned_end =
addr + Common::AlignUp(boost::icl::last_next(interval) - addr, stride_tiled_bytes);
start + Common::AlignUp(boost::icl::last_next(interval) - start, stride_tiled_bytes);
if (aligned_end - aligned_start > stride_tiled_bytes) {
params.addr = aligned_start;
params.height = (aligned_end - aligned_start) / BytesInPixels(stride);
params.height = (aligned_end - aligned_start) / BytesInPixels(params.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);
start + Common::AlignDown(boost::icl::first(interval) - start, tiled_alignment);
aligned_end =
addr + Common::AlignUp(boost::icl::last_next(interval) - addr, tiled_alignment);
start + Common::AlignUp(boost::icl::last_next(interval) - start, tiled_alignment);
params.addr = aligned_start;
params.width = PixelsInBytes(aligned_end - aligned_start) / tiled_size;
@ -112,11 +136,13 @@ SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const {
params.height = tiled_size;
}
params.levels = 1;
params.UpdateParams();
return params;
}
SurfaceInterval SurfaceParams::GetSubRectInterval(Rect2D unscaled_rect) const {
ASSERT(levels == 1);
if (unscaled_rect.GetHeight() == 0 || unscaled_rect.GetWidth() == 0) [[unlikely]] {
return {};
}
@ -137,4 +163,52 @@ SurfaceInterval SurfaceParams::GetSubRectInterval(Rect2D unscaled_rect) const {
return {addr + BytesInPixels(pixel_offset), addr + BytesInPixels(pixel_offset + pixels)};
}
void SurfaceParams::CalculateMipLevelOffsets() {
ASSERT(levels <= MAX_PICA_LEVELS && stride == width);
u32 level_width = width;
u32 level_height = height;
u32 offset = addr;
for (u32 level = 0; level < levels; level++) {
mipmap_offsets[level] = offset;
offset += BytesInPixels(level_width * level_height);
level_width >>= 1;
level_height >>= 1;
}
}
u32 SurfaceParams::CalculateSurfaceSize() const {
ASSERT(levels <= MAX_PICA_LEVELS && stride == width);
u32 level_width = width;
u32 level_height = height;
u32 size = 0;
for (u32 level = 0; level < levels; level++) {
size += BytesInPixels(level_width * level_height);
level_width >>= 1;
level_height >>= 1;
}
return size;
}
SurfaceInterval SurfaceParams::LevelInterval(u32 level) const {
ASSERT(levels > level);
const PAddr start_addr = mipmap_offsets[level];
const PAddr end_addr = level == (levels - 1) ? end : mipmap_offsets[level + 1];
return {start_addr, end_addr};
}
u32 SurfaceParams::LevelOf(PAddr level_addr) const {
ASSERT(level_addr >= addr && level_addr <= end);
u32 level = levels - 1;
while (mipmap_offsets[level] > level_addr) {
level--;
}
return level;
}
} // namespace VideoCore

View File

@ -8,6 +8,8 @@
namespace VideoCore {
constexpr std::size_t MAX_PICA_LEVELS = 8;
class SurfaceParams {
public:
/// Returns true if other_surface matches exactly params
@ -37,6 +39,12 @@ public:
/// Returns the address interval referenced by unscaled_rect
SurfaceInterval GetSubRectInterval(Rect2D unscaled_rect) const;
/// Return the address interval of the provided level
SurfaceInterval LevelInterval(u32 level) const;
/// Returns the level of the provided address
u32 LevelOf(PAddr addr) const;
[[nodiscard]] SurfaceInterval GetInterval() const noexcept {
return SurfaceInterval{addr, end};
}
@ -69,6 +77,13 @@ public:
return pixels * GetFormatBpp() / 8;
}
private:
/// Computes the offset of each mipmap level
void CalculateMipLevelOffsets();
/// Calculates total surface size taking mipmaps into account
u32 CalculateSurfaceSize() const;
public:
PAddr addr = 0;
PAddr end = 0;
@ -84,6 +99,8 @@ public:
TextureType texture_type = TextureType::Texture2D;
PixelFormat pixel_format = PixelFormat::Invalid;
SurfaceType type = SurfaceType::Invalid;
std::array<u32, MAX_PICA_LEVELS> mipmap_offsets{};
};
} // namespace VideoCore

View File

@ -99,6 +99,7 @@ struct TextureCubeConfig {
PAddr pz;
PAddr nz;
u32 width;
u32 levels;
Pica::TexturingRegs::TextureFormat format;
auto operator<=>(const TextureCubeConfig&) const noexcept = default;

View File

@ -65,6 +65,7 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum
level = Log::Level::Debug;
break;
}
LOG_GENERIC(Log::Class::Render_OpenGL, level, "{} {} {}: {}", GetSource(source), GetType(type),
id, message);
}

View File

@ -452,6 +452,7 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
.pz = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveZ),
.nz = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeZ),
.width = texture.config.width,
.levels = texture.config.lod.max_level + 1,
.format = texture.format};
state.texture_cube_unit.texture_cube =

View File

@ -16,6 +16,8 @@
namespace OpenGL {
using VideoCore::TextureType;
constexpr FormatTuple DEFAULT_TUPLE = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
static constexpr std::array DEPTH_TUPLES = {
@ -137,10 +139,6 @@ OGLTexture TextureRuntime::Allocate(u32 width, u32 height, u32 levels,
return texture;
}
const auto& tuple = GetFormatTuple(format);
const OpenGLState& state = OpenGLState::GetCurState();
GLuint old_tex = state.texture_units[0].texture_2d;
// Allocate new texture
OGLTexture texture{};
texture.Create();
@ -148,13 +146,14 @@ OGLTexture TextureRuntime::Allocate(u32 width, u32 height, u32 levels,
glActiveTexture(GL_TEXTURE0);
glBindTexture(target, texture.handle);
const auto& tuple = GetFormatTuple(format);
glTexStorage2D(target, levels, tuple.internal_format, width, height);
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(target, old_tex);
glBindTexture(target, OpenGLState::GetCurState().texture_units[0].texture_2d);
return texture;
}
@ -221,10 +220,14 @@ bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClea
bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
const VideoCore::TextureCopy& copy) {
glCopyImageSubData(source.texture.handle, GL_TEXTURE_2D, copy.src_level, copy.src_offset.x,
copy.src_offset.y, 0, dest.texture.handle, GL_TEXTURE_2D, copy.dst_level,
copy.dst_offset.x, copy.dst_offset.y, 0, copy.extent.width,
copy.extent.height, 1);
const GLenum src_textarget =
source.texture_type == TextureType::CubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
const GLenum dst_textarget =
dest.texture_type == TextureType::CubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
glCopyImageSubData(source.texture.handle, src_textarget, copy.src_level, copy.src_offset.x,
copy.src_offset.y, copy.src_layer, dest.texture.handle, dst_textarget,
copy.dst_level, copy.dst_offset.x, copy.dst_offset.y, copy.dst_layer,
copy.extent.width, copy.extent.height, 1);
return true;
}
@ -238,13 +241,13 @@ bool TextureRuntime::BlitTextures(Surface& source, Surface& dest,
state.draw.draw_framebuffer = draw_fbo.handle;
state.Apply();
const GLenum src_textarget = source.texture_type == VideoCore::TextureType::CubeMap
const GLenum src_textarget = source.texture_type == TextureType::CubeMap
? GL_TEXTURE_CUBE_MAP_POSITIVE_X + blit.src_layer
: GL_TEXTURE_2D;
BindFramebuffer(GL_READ_FRAMEBUFFER, blit.src_level, src_textarget, source.type,
source.texture);
const GLenum dst_textarget = dest.texture_type == VideoCore::TextureType::CubeMap
const GLenum dst_textarget = dest.texture_type == TextureType::CubeMap
? GL_TEXTURE_CUBE_MAP_POSITIVE_X + blit.dst_layer
: GL_TEXTURE_2D;
BindFramebuffer(GL_DRAW_FRAMEBUFFER, blit.dst_level, dst_textarget, dest.type, dest.texture);
@ -347,7 +350,8 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, const StagingDa
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));
const VideoCore::Rect2D rect = upload.texture_rect;
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(rect.GetWidth()));
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, staging.buffer);
// Unmap the buffer FindStaging mapped beforehand
@ -357,15 +361,12 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, const StagingDa
glBindTexture(GL_TEXTURE_2D, texture.handle);
const auto& tuple = runtime.GetFormatTuple(pixel_format);
glTexSubImage2D(GL_TEXTURE_2D, upload.texture_level, upload.texture_rect.left,
upload.texture_rect.bottom, upload.texture_rect.GetWidth(),
upload.texture_rect.GetHeight(), tuple.format, tuple.type,
glTexSubImage2D(GL_TEXTURE_2D, upload.texture_level, rect.left, rect.bottom,
rect.GetWidth(), rect.GetHeight(), tuple.format, tuple.type,
reinterpret_cast<void*>(staging.buffer_offset));
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
InvalidateAllWatcher();
}
MICROPROFILE_DEFINE(OpenGL_Download, "OpenGL", "Texture Download", MP_RGB(128, 192, 64));
@ -375,25 +376,25 @@ void Surface::Download(const VideoCore::BufferTextureCopy& download, const Stagi
// Ensure no bad interactions with GL_PACK_ALIGNMENT
ASSERT(stride * GetBytesPerPixel(pixel_format) % 4 == 0);
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(stride));
const bool is_scaled = res_scale != 1;
if (is_scaled) {
ScaledDownload(download, staging);
} else {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
const VideoCore::Rect2D rect = download.texture_rect;
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(rect.GetWidth()));
runtime.BindFramebuffer(GL_READ_FRAMEBUFFER, download.texture_level, GL_TEXTURE_2D, type,
texture);
const auto& tuple = runtime.GetFormatTuple(pixel_format);
glReadPixels(download.texture_rect.left, download.texture_rect.bottom,
download.texture_rect.GetWidth(), download.texture_rect.GetHeight(),
tuple.format, tuple.type, staging.mapped.data());
}
glReadPixels(rect.left, rect.bottom, rect.GetWidth(), rect.GetHeight(), tuple.format,
tuple.type, staging.mapped.data());
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
}
}
void Surface::ScaledUpload(const VideoCore::BufferTextureCopy& upload, const StagingData& staging) {
@ -414,7 +415,6 @@ void Surface::ScaledUpload(const VideoCore::BufferTextureCopy& upload, const Sta
.buffer_size = upload.buffer_size,
.texture_rect = unscaled_rect,
};
unscaled_surface.Upload(unscaled_upload, staging);
const auto& filterer = runtime.GetFilterer();
@ -422,13 +422,9 @@ void Surface::ScaledUpload(const VideoCore::BufferTextureCopy& upload, const Sta
const VideoCore::TextureBlit blit = {
.src_level = 0,
.dst_level = upload.texture_level,
.src_layer = 0,
.dst_layer = 0,
.src_rect = unscaled_rect,
.dst_rect = scaled_rect,
};
// If filtering fails, resort to normal blitting
runtime.BlitTextures(unscaled_surface, *this, blit);
}
}
@ -451,7 +447,7 @@ void Surface::ScaledDownload(const VideoCore::BufferTextureCopy& download,
// Blit the scaled rectangle to the unscaled texture
const VideoCore::TextureBlit blit = {
.src_level = download.texture_level,
.dst_level = 0,
.dst_level = download.texture_level,
.src_layer = 0,
.dst_layer = 0,
.src_rect = scaled_rect,
@ -464,12 +460,13 @@ void Surface::ScaledDownload(const VideoCore::BufferTextureCopy& download,
const auto& tuple = runtime.GetFormatTuple(pixel_format);
if (driver.IsOpenGLES()) {
runtime.BindFramebuffer(GL_READ_FRAMEBUFFER, 0, GL_TEXTURE_2D, type,
runtime.BindFramebuffer(GL_READ_FRAMEBUFFER, download.texture_level, GL_TEXTURE_2D, type,
unscaled_surface.texture);
glReadPixels(0, 0, rect_width, rect_height, tuple.format, tuple.type,
staging.mapped.data());
} else {
glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, staging.mapped.data());
glGetTexImage(GL_TEXTURE_2D, download.texture_level, tuple.format, tuple.type,
staging.mapped.data());
}
}

View File

@ -618,6 +618,7 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer& framebuffer) {
.pz = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveZ),
.nz = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeZ),
.width = texture.config.width,
.levels = texture.config.lod.max_level + 1,
.format = texture.format};
auto surface = res_cache.GetTextureCube(config);

View File

@ -871,8 +871,6 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, const StagingDa
runtime.upload_buffer.Commit(staging.size);
}
InvalidateAllWatcher();
}
void Surface::Download(const VideoCore::BufferTextureCopy& download, const StagingData& staging) {