diff --git a/src/video_core/rasterizer_cache/pixel_format.cpp b/src/video_core/rasterizer_cache/pixel_format.cpp index 7f8eb43bf..32ab2cd0d 100644 --- a/src/video_core/rasterizer_cache/pixel_format.cpp +++ b/src/video_core/rasterizer_cache/pixel_format.cpp @@ -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; } diff --git a/src/video_core/rasterizer_cache/rasterizer_cache.h b/src/video_core/rasterizer_cache/rasterizer_cache.h index 7248fd4bd..f5993e0cb 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache.h @@ -122,14 +122,13 @@ bool RasterizerCache::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::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::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::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::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::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::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(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 @@ -630,6 +569,7 @@ auto RasterizerCache::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::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(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(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(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::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::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(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(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 @@ -956,7 +879,7 @@ void RasterizerCache::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::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::DownloadFillSurface(const Surface& surface, SurfaceInte template bool RasterizerCache::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::NoUnimplementedReinterpretations(const Surface& surface } template -bool RasterizerCache::IntervalHasInvalidPixelFormat(SurfaceParams& params, +bool RasterizerCache::IntervalHasInvalidPixelFormat(SurfaceParams params, SurfaceInterval interval) { bool invalid_format_found = false; ForEachSurfaceInRegion(params.addr, params.end, [&](Surface surface) { @@ -1072,7 +995,7 @@ bool RasterizerCache::IntervalHasInvalidPixelFormat(SurfaceParams& params, } template -bool RasterizerCache::ValidateByReinterpretation(const Surface& surface, SurfaceParams& params, +bool RasterizerCache::ValidateByReinterpretation(const Surface& surface, SurfaceParams params, SurfaceInterval interval) { const PixelFormat dest_format = surface->pixel_format; for (const auto& reinterpreter : runtime.GetPossibleReinterpretations(dest_format)) { diff --git a/src/video_core/rasterizer_cache/rasterizer_cache_base.h b/src/video_core/rasterizer_cache/rasterizer_cache_base.h index b389577dc..d3b39bece 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache_base.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache_base.h @@ -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 diff --git a/src/video_core/rasterizer_cache/surface_base.cpp b/src/video_core/rasterizer_cache/surface_base.cpp index 4a9580f43..b61f0e2a5 100644 --- a/src/video_core/rasterizer_cache/surface_base.cpp +++ b/src/video_core/rasterizer_cache/surface_base.cpp @@ -151,30 +151,4 @@ std::array SurfaceBase::MakeFillBuffer(PAddr copy_addr) { return fill_buffer; } -std::shared_ptr SurfaceBase::CreateWatcher() { - auto weak_ptr = weak_from_this(); - auto watcher = std::make_shared(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 diff --git a/src/video_core/rasterizer_cache/surface_base.h b/src/video_core/rasterizer_cache/surface_base.h index b00cb2b75..52d6400ac 100644 --- a/src/video_core/rasterizer_cache/surface_base.h +++ b/src/video_core/rasterizer_cache/surface_base.h @@ -12,38 +12,7 @@ namespace VideoCore { using SurfaceRegions = boost::icl::interval_set; -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&& 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 Get() const { - return surface.lock(); - } - -public: - std::weak_ptr surface; - bool valid = false; -}; - -class SurfaceBase : public SurfaceParams, public std::enable_shared_from_this { +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 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, 7> level_watchers; std::array fill_data; u32 fill_size = 0; - -public: - std::vector> watchers; }; } // namespace VideoCore diff --git a/src/video_core/rasterizer_cache/surface_params.cpp b/src/video_core/rasterizer_cache/surface_params.cpp index faa281404..85c464083 100644 --- a/src/video_core/rasterizer_cache/surface_params.cpp +++ b/src/video_core/rasterizer_cache/surface_params.cpp @@ -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 diff --git a/src/video_core/rasterizer_cache/surface_params.h b/src/video_core/rasterizer_cache/surface_params.h index ea43d44b3..647145cab 100644 --- a/src/video_core/rasterizer_cache/surface_params.h +++ b/src/video_core/rasterizer_cache/surface_params.h @@ -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 mipmap_offsets{}; }; } // namespace VideoCore diff --git a/src/video_core/rasterizer_cache/utils.h b/src/video_core/rasterizer_cache/utils.h index 0c59dd057..10047266d 100644 --- a/src/video_core/rasterizer_cache/utils.h +++ b/src/video_core/rasterizer_cache/utils.h @@ -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; diff --git a/src/video_core/renderer_opengl/gl_driver.cpp b/src/video_core/renderer_opengl/gl_driver.cpp index 423977640..39ba6967f 100644 --- a/src/video_core/renderer_opengl/gl_driver.cpp +++ b/src/video_core/renderer_opengl/gl_driver.cpp @@ -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); } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 3d967ed77..ecb584101 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -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 = diff --git a/src/video_core/renderer_opengl/gl_texture_runtime.cpp b/src/video_core/renderer_opengl/gl_texture_runtime.cpp index 345951860..8638240ca 100644 --- a/src/video_core/renderer_opengl/gl_texture_runtime.cpp +++ b/src/video_core/renderer_opengl/gl_texture_runtime.cpp @@ -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(stride)); + const VideoCore::Rect2D rect = upload.texture_rect; + glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast(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(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(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(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()); } } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index c4152643c..024ad4069 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -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); diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp index 870804b19..786af13cf 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp @@ -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) {