From ca78d3493347285e67457f6fb9a064e70530d332 Mon Sep 17 00:00:00 2001 From: wwylele Date: Mon, 2 Jul 2018 12:55:04 +0300 Subject: [PATCH] gl_rasterizer: implement mipmap --- src/video_core/regs_texturing.h | 7 +- .../renderer_opengl/gl_rasterizer.cpp | 32 ++++- .../renderer_opengl/gl_rasterizer.h | 4 + .../renderer_opengl/gl_rasterizer_cache.cpp | 109 +++++++++++++++--- .../renderer_opengl/gl_rasterizer_cache.h | 7 +- src/video_core/renderer_opengl/pica_to_gl.h | 51 ++++---- 6 files changed, 163 insertions(+), 47 deletions(-) diff --git a/src/video_core/regs_texturing.h b/src/video_core/regs_texturing.h index d07aa28b7..3954e13b4 100644 --- a/src/video_core/regs_texturing.h +++ b/src/video_core/regs_texturing.h @@ -59,11 +59,16 @@ struct TexturingRegs { BitField<2, 1, TextureFilter> min_filter; BitField<8, 3, WrapMode> wrap_t; BitField<12, 3, WrapMode> wrap_s; + BitField<24, 1, TextureFilter> mip_filter; /// @note Only valid for texture 0 according to 3DBrew. BitField<28, 3, TextureType> type; }; - INSERT_PADDING_WORDS(0x1); + union { + BitField<0, 13, s32> bias; // fixed1.4.8 + BitField<16, 4, u32> max_level; + BitField<24, 4, u32> min_level; + } lod; BitField<0, 28, u32> address; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 923f2efd3..475d2a88b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -1555,13 +1555,16 @@ bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& con void RasterizerOpenGL::SamplerInfo::Create() { sampler.Create(); - mag_filter = min_filter = TextureConfig::Linear; + mag_filter = min_filter = mip_filter = TextureConfig::Linear; wrap_s = wrap_t = TextureConfig::Repeat; border_color = 0; + lod_min = lod_max = 0; + lod_bias = 0; - // default is GL_LINEAR_MIPMAP_LINEAR - glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + // default is 1000 and -1000 // Other attributes have correct defaults + glSamplerParameterf(sampler.handle, GL_TEXTURE_MAX_LOD, lod_max); + glSamplerParameterf(sampler.handle, GL_TEXTURE_MIN_LOD, lod_min); } void RasterizerOpenGL::SamplerInfo::SyncWithConfig( @@ -1571,11 +1574,13 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig( if (mag_filter != config.mag_filter) { mag_filter = config.mag_filter; - glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, PicaToGL::TextureFilterMode(mag_filter)); + glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, PicaToGL::TextureMagFilterMode(mag_filter)); } - if (min_filter != config.min_filter) { + if (min_filter != config.min_filter || mip_filter != config.mip_filter) { min_filter = config.min_filter; - glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, PicaToGL::TextureFilterMode(min_filter)); + mip_filter = config.mip_filter; + glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, + PicaToGL::TextureMinFilterMode(min_filter, mip_filter)); } if (wrap_s != config.wrap_s) { @@ -1594,6 +1599,21 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig( glSamplerParameterfv(s, GL_TEXTURE_BORDER_COLOR, gl_color.data()); } } + + if (lod_min != config.lod.min_level) { + lod_min = config.lod.min_level; + glSamplerParameterf(s, GL_TEXTURE_MIN_LOD, lod_min); + } + + if (lod_max != config.lod.max_level) { + lod_max = config.lod.max_level; + glSamplerParameterf(s, GL_TEXTURE_MAX_LOD, lod_max); + } + + if (lod_bias != config.lod.bias) { + lod_bias = config.lod.bias; + glSamplerParameterf(s, GL_TEXTURE_LOD_BIAS, lod_bias / 256.0f); + } } void RasterizerOpenGL::SetShader() { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 730364819..f890c1049 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -69,9 +69,13 @@ private: private: TextureConfig::TextureFilter mag_filter; TextureConfig::TextureFilter min_filter; + TextureConfig::TextureFilter mip_filter; TextureConfig::WrapMode wrap_s; TextureConfig::WrapMode wrap_t; u32 border_color; + u32 lod_min; + u32 lod_max; + s32 lod_bias; }; /// Structure that the hardware rendered vertices are composed of diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 44dd2e78f..5f7ceecaa 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -1326,10 +1326,11 @@ Surface RasterizerCacheOpenGL::GetTextureSurface( const Pica::TexturingRegs::FullTextureConfig& config) { Pica::Texture::TextureInfo info = Pica::Texture::TextureInfo::FromPicaRegister(config.config, config.format); - return GetTextureSurface(info); + return GetTextureSurface(info, config.config.lod.max_level); } -Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInfo& info) { +Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInfo& info, + u32 max_level) { SurfaceParams params; params.addr = info.physical_address; params.width = info.width; @@ -1338,23 +1339,97 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInf params.pixel_format = SurfaceParams::PixelFormatFromTextureFormat(info.format); params.UpdateParams(); - if (info.width % 8 != 0 || info.height % 8 != 0) { - Surface src_surface; - Common::Rectangle rect; - std::tie(src_surface, rect) = GetSurfaceSubRect(params, ScaleMatch::Ignore, true); - - params.res_scale = src_surface->res_scale; - Surface tmp_surface = CreateSurface(params); - BlitTextures(src_surface->texture.handle, rect, tmp_surface->texture.handle, - tmp_surface->GetScaledRect(), - SurfaceParams::GetFormatType(params.pixel_format), read_framebuffer.handle, - draw_framebuffer.handle); - - remove_surfaces.emplace(tmp_surface); - return tmp_surface; + u32 min_width = info.width >> max_level; + u32 min_height = info.height >> max_level; + if (min_width % 8 != 0 || min_height % 8 != 0) { + LOG_CRITICAL(Render_OpenGL, "Texture size ({}x{}) is not multiple of 8", min_width, + min_height); + return nullptr; + } + if (info.width != (min_width << max_level) || info.height != (min_height << max_level)) { + LOG_CRITICAL(Render_OpenGL, + "Texture size ({}x{}) does not support required mipmap level ({})", + params.width, params.height, max_level); + return nullptr; } - return GetSurface(params, ScaleMatch::Ignore, true); + auto surface = GetSurface(params, ScaleMatch::Ignore, true); + + // 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(Render_OpenGL, "Unsupported mipmap level {}", max_level); + return nullptr; + } + OpenGLState prev_state = OpenGLState::GetCurState(); + OpenGLState state; + SCOPE_EXIT({ prev_state.Apply(); }); + auto format_tuple = GetFormatTuple(params.pixel_format); + + // Allocate more mipmap level if necessary + if (surface->max_level < max_level) { + state.texture_units[0].texture_2d = surface->texture.handle; + state.Apply(); + glActiveTexture(GL_TEXTURE0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, max_level); + u32 width = surface->width * surface->res_scale; + u32 height = surface->height * surface->res_scale; + for (u32 level = surface->max_level + 1; level <= max_level; ++level) { + glTexImage2D(GL_TEXTURE_2D, level, format_tuple.internal_format, width >> level, + height >> level, 0, format_tuple.format, format_tuple.type, nullptr); + } + surface->max_level = max_level; + } + + // Blit mipmaps that have been invalidated + state.draw.read_framebuffer = read_framebuffer.handle; + state.draw.draw_framebuffer = draw_framebuffer.handle; + state.ResetTexture(surface->texture.handle); + SurfaceParams params = *surface; + for (u32 level = 1; level <= max_level; ++level) { + // In PICA all mipmap levels are stored next to each other + params.addr += params.width * params.height * params.GetFormatBpp() / 8; + params.width /= 2; + params.height /= 2; + params.stride = 0; // reset stride and let UpdateParams re-initialize it + params.UpdateParams(); + auto& watcher = surface->level_watchers[level - 1]; + if (!watcher || !watcher->Get()) { + auto level_surface = GetSurface(params, ScaleMatch::Ignore, true); + if (level_surface) { + watcher = level_surface->CreateWatcher(); + } else { + watcher = nullptr; + } + } + + if (watcher && !watcher->IsValid()) { + auto level_surface = watcher->Get(); + state.ResetTexture(level_surface->texture.handle); + state.Apply(); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + level_surface->texture.handle, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_TEXTURE_2D, 0, 0); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + surface->texture.handle, level); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_TEXTURE_2D, 0, 0); + + auto src_rect = level_surface->GetScaledRect(); + auto dst_rect = params.GetScaledRect(); + glBlitFramebuffer(src_rect.left, src_rect.bottom, src_rect.right, src_rect.top, + dst_rect.left, dst_rect.bottom, dst_rect.right, dst_rect.top, + GL_COLOR_BUFFER_BIT, GL_LINEAR); + watcher->Validate(); + } + } + } + + return surface; } const CachedTextureCube& RasterizerCacheOpenGL::GetTextureCube(const TextureCubeConfig& config) { diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 555e9078b..f3dbdd8dd 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -356,6 +356,11 @@ struct CachedSurface : SurfaceParams, std::enable_shared_from_this, 7> level_watchers; + static constexpr unsigned int GetGLBytesPerPixel(PixelFormat format) { // OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type return format == PixelFormat::Invalid @@ -434,7 +439,7 @@ public: /// Get a surface based on the texture configuration Surface GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config); - Surface GetTextureSurface(const Pica::Texture::TextureInfo& info); + 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); diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h index 0bdc63d4a..c7cda0ba9 100644 --- a/src/video_core/renderer_opengl/pica_to_gl.h +++ b/src/video_core/renderer_opengl/pica_to_gl.h @@ -29,33 +29,40 @@ using GLivec4 = std::array; namespace PicaToGL { -inline GLenum TextureFilterMode(Pica::TexturingRegs::TextureConfig::TextureFilter mode) { - static constexpr std::array filter_mode_table{{ - GL_NEAREST, // TextureFilter::Nearest - GL_LINEAR, // TextureFilter::Linear - }}; - - const auto index = static_cast(mode); - - // Range check table for input - if (index >= filter_mode_table.size()) { - LOG_CRITICAL(Render_OpenGL, "Unknown texture filtering mode {}", index); - UNREACHABLE(); +using TextureFilter = Pica::TexturingRegs::TextureConfig::TextureFilter; +inline GLenum TextureMagFilterMode(TextureFilter mode) { + if (mode == TextureFilter::Linear) { return GL_LINEAR; } - - GLenum gl_mode = filter_mode_table[index]; - - // Check for dummy values indicating an unknown mode - if (gl_mode == 0) { - LOG_CRITICAL(Render_OpenGL, "Unknown texture filtering mode {}", index); - UNIMPLEMENTED(); - - return GL_LINEAR; + if (mode == TextureFilter::Nearest) { + return GL_NEAREST; } + LOG_CRITICAL(Render_OpenGL, "Unknown texture filtering mode {}", static_cast(mode)); + UNIMPLEMENTED(); + return GL_LINEAR; +} - return gl_mode; +inline GLenum TextureMinFilterMode(TextureFilter min, TextureFilter mip) { + if (min == TextureFilter::Linear) { + if (mip == TextureFilter::Linear) { + return GL_LINEAR_MIPMAP_LINEAR; + } + if (mip == TextureFilter::Nearest) { + return GL_LINEAR_MIPMAP_NEAREST; + } + } else if (min == TextureFilter::Nearest) { + if (mip == TextureFilter::Linear) { + return GL_NEAREST_MIPMAP_LINEAR; + } + if (mip == TextureFilter::Nearest) { + return GL_NEAREST_MIPMAP_NEAREST; + } + } + LOG_CRITICAL(Render_OpenGL, "Unknown texture filtering mode {} and {}", static_cast(min), + static_cast(mip)); + UNIMPLEMENTED(); + return GL_LINEAR_MIPMAP_LINEAR; } inline GLenum WrapMode(Pica::TexturingRegs::TextureConfig::WrapMode mode) {