OpenGL Cache: Refactor Surface Cache interface

Changes the public interface of the surface cache to make it easier to
use. Reintroduces the cached page count cached pages that was removed in
an earlier commit.
This commit is contained in:
James Rowe 2017-11-25 13:21:32 -07:00
parent 3e1cbb7d14
commit 81ea32d1e0
2 changed files with 170 additions and 53 deletions

View File

@ -1179,42 +1179,140 @@ Surface FindMatch(const SurfaceCache& surface_cache, const SurfaceParams& params
return match_surface; return match_surface;
} }
void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, const CachedSurface* skip_surface, void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, Surface flush_surface) {
bool invalidate) { if (size == 0)
if (size == 0) {
return; return;
const auto flush_interval = SurfaceInterval(addr, addr + size);
for (auto& pair : RangeFromInterval(dirty_regions, flush_interval)) {
const auto interval = pair.first & flush_interval;
auto& surface = pair.second;
if (flush_surface != nullptr && surface != flush_surface)
continue;
// Sanity check, this surface is the last one that marked this region dirty
ASSERT(surface->IsRegionValid(interval));
if (surface->type != SurfaceType::Fill) {
SurfaceParams params = surface->FromInterval(interval);
surface->DownloadGLTexture(surface->GetSubRect(params));
}
surface->FlushGLBuffer(boost::icl::first(interval), boost::icl::last_next(interval));
} }
// Gather up unique surfaces that touch the region // Reset dirty regions
std::unordered_set<std::shared_ptr<CachedSurface>> touching_surfaces; dirty_regions.erase(flush_interval);
auto surface_interval = boost::icl::interval<PAddr>::right_open(addr, addr + size);
auto cache_upper_bound = surface_cache.upper_bound(surface_interval);
for (auto it = surface_cache.lower_bound(surface_interval); it != cache_upper_bound; ++it) {
std::copy_if(it->second.begin(), it->second.end(),
std::inserter(touching_surfaces, touching_surfaces.end()),
[skip_surface](std::shared_ptr<CachedSurface> surface) {
return (surface.get() != skip_surface);
});
}
// Flush and invalidate surfaces
for (auto surface : touching_surfaces) {
FlushSurface(surface.get());
if (invalidate) {
Memory::RasterizerMarkRegionCached(surface->addr, surface->size, -1);
surface_cache.subtract(
std::make_pair(boost::icl::interval<PAddr>::right_open(
surface->addr, surface->addr + surface->size),
std::set<std::shared_ptr<CachedSurface>>({surface})));
}
}
} }
void RasterizerCacheOpenGL::FlushAll() { void RasterizerCacheOpenGL::FlushAll() {
for (auto& surfaces : surface_cache) { FlushRegion(0, 0xFFFFFFFF);
for (auto& surface : surfaces.second) { }
FlushSurface(surface.get());
} void RasterizerCacheOpenGL::InvalidateRegion(PAddr addr, u32 size, const Surface& region_owner) {
} if (size == 0)
return;
const auto invalid_interval = SurfaceInterval(addr, addr + size);
if (region_owner != nullptr) {
ASSERT(region_owner->type != SurfaceType::Texture);
ASSERT(addr >= region_owner->addr && addr + size <= region_owner->end);
ASSERT(region_owner->width == region_owner->stride); // Surfaces can't have a gap
region_owner->invalid_regions.erase(invalid_interval);
}
for (auto& pair : RangeFromInterval(surface_cache, invalid_interval)) {
for (auto& cached_surface : pair.second) {
if (cached_surface == region_owner)
continue;
const auto interval = cached_surface->GetInterval() & invalid_interval;
cached_surface->invalid_regions.insert(interval);
// Remove only "empty" fill surfaces to avoid destroying and recreating OGL textures
if (cached_surface->type == SurfaceType::Fill &&
cached_surface->IsSurfaceFullyInvalid()) {
remove_surfaces.emplace(cached_surface);
}
}
}
if (region_owner != nullptr)
dirty_regions.set({invalid_interval, region_owner});
else
dirty_regions.erase(invalid_interval);
for (auto& remove_surface : remove_surfaces) {
if (remove_surface == region_owner) {
Surface expanded_surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(
surface_cache, *region_owner, ScaleMatch::Ignore);
ASSERT(expanded_surface);
if ((region_owner->invalid_regions - expanded_surface->invalid_regions).empty()) {
DuplicateSurface(region_owner, expanded_surface);
} else {
continue;
}
}
UnregisterSurface(remove_surface);
}
remove_surfaces.clear();
}
Surface RasterizerCacheOpenGL::CreateSurface(const SurfaceParams& params) {
Surface surface = std::make_shared<CachedSurface>();
static_cast<SurfaceParams&>(*surface) = params;
surface->texture.Create();
surface->gl_buffer_size = 0;
surface->invalid_regions.insert(surface->GetInterval());
AllocateSurfaceTexture(surface->texture.handle, GetFormatTuple(surface->pixel_format),
surface->GetScaledWidth(), surface->GetScaledHeight());
return surface;
}
void RasterizerCacheOpenGL::RegisterSurface(const Surface& surface) {
surface_cache.add({surface->GetInterval(), SurfaceSet{surface}});
UpdatePagesCachedCount(surface->addr, surface->size, 1);
}
void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) {
UpdatePagesCachedCount(surface->addr, surface->size, -1);
surface_cache.subtract({surface->GetInterval(), SurfaceSet{surface}});
}
void RasterizerCacheOpenGL::UpdatePagesCachedCount(PAddr addr, u32 size, int delta) {
const u32 num_pages =
((addr + size - 1) >> Memory::PAGE_BITS) - (addr >> Memory::PAGE_BITS) + 1;
const u32 page_start = addr >> Memory::PAGE_BITS;
const u32 page_end = page_start + num_pages;
// Interval maps will erase segments if count reaches 0, so if delta is negative we have to
// subtract after iterating
const auto pages_interval = PageMap::interval_type::right_open(page_start, page_end);
if (delta > 0)
cached_pages.add({pages_interval, delta});
for (auto& pair : RangeFromInterval(cached_pages, pages_interval)) {
const auto interval = pair.first & pages_interval;
const int count = pair.second;
const PAddr interval_start_addr = boost::icl::first(interval) << Memory::PAGE_BITS;
const PAddr interval_end_addr = boost::icl::last_next(interval) << Memory::PAGE_BITS;
const u32 interval_size = interval_end_addr - interval_start_addr;
if (delta > 0 && count == delta)
Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, true);
else if (delta < 0 && count == -delta)
Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, false);
else
ASSERT(count >= 0);
}
if (delta < 0)
cached_pages.add({pages_interval, delta});
} }

View File

@ -298,46 +298,65 @@ public:
RasterizerCacheOpenGL(); RasterizerCacheOpenGL();
~RasterizerCacheOpenGL(); ~RasterizerCacheOpenGL();
/// Blits one texture to another /// Blit one surface's texture to another
void BlitTextures(GLuint src_tex, GLuint dst_tex, CachedSurface::SurfaceType type, bool BlitSurfaces(const Surface& src_surface, const MathUtil::Rectangle<u32>& src_rect,
const MathUtil::Rectangle<int>& src_rect, const Surface& dst_surface, const MathUtil::Rectangle<u32>& dst_rect);
const MathUtil::Rectangle<int>& dst_rect);
/// Attempt to blit one surface's texture to another /// Copy one surface's region to another
bool TryBlitSurfaces(CachedSurface* src_surface, const MathUtil::Rectangle<int>& src_rect, void CopySurface(const Surface& src_surface, const Surface& dst_surface,
CachedSurface* dst_surface, const MathUtil::Rectangle<int>& dst_rect); SurfaceInterval copy_interval);
/// Loads a texture from 3DS memory to OpenGL and caches it (if not already cached) /// Load a texture from 3DS memory to OpenGL and cache it (if not already cached)
CachedSurface* GetSurface(const CachedSurface& params, bool match_res_scale, Surface GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale,
bool load_if_create); bool load_if_create);
/// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from /// 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) /// 3DS memory to OpenGL and caches it (if not already cached)
CachedSurface* GetSurfaceRect(const CachedSurface& params, bool match_res_scale, SurfaceRect_Tuple GetSurfaceSubRect(const SurfaceParams& params, ScaleMatch match_res_scale,
bool load_if_create, MathUtil::Rectangle<int>& out_rect); bool load_if_create);
/// Gets a surface based on the texture configuration /// Get a surface based on the texture configuration
CachedSurface* GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config); Surface GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config);
/// Gets the color and depth surfaces and rect (resolution scaled) based on the framebuffer /// Get the color and depth surfaces based on the framebuffer configuration
/// configuration SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb,
std::tuple<CachedSurface*, CachedSurface*, MathUtil::Rectangle<int>> GetFramebufferSurfaces( const MathUtil::Rectangle<s32>& viewport_rect);
const Pica::FramebufferRegs::FramebufferConfig& config);
/// Attempt to get a surface that exactly matches the fill region and format /// Get a surface that matches the fill config
CachedSurface* TryGetFillSurface(const GPU::Regs::MemoryFillConfig& config); Surface GetFillSurface(const GPU::Regs::MemoryFillConfig& config);
/// Write the surface back to memory /// Get a surface that matches a "texture copy" display transfer config
void FlushSurface(CachedSurface* surface); SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params);
/// Write any cached resources overlapping the region back to memory (if dirty) and optionally /// Write any cached resources overlapping the region back to memory (if dirty)
/// invalidate them in the cache void FlushRegion(PAddr addr, u32 size, Surface flush_surface = nullptr);
void FlushRegion(PAddr addr, u32 size, const CachedSurface* skip_surface, bool invalidate);
/// 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 /// Flush all cached resources tracked by this cache manager
void FlushAll(); void FlushAll();
private: 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);
/// 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);
SurfaceCache surface_cache; SurfaceCache surface_cache;
OGLFramebuffer transfer_framebuffers[2]; SurfaceMap dirty_regions;
PageMap cached_pages;
SurfaceSet remove_surfaces;
}; };