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..8730d1fe9 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,28 @@ 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)); + } + + // TODO(wwylele): remove this block once mipmap for cube is implemented + bool new_supress_mipmap_for_cube = + config.type == Pica::TexturingRegs::TextureConfig::TextureCube; + if (supress_mipmap_for_cube != new_supress_mipmap_for_cube) { + supress_mipmap_for_cube = new_supress_mipmap_for_cube; + if (new_supress_mipmap_for_cube) { + // HACK: use mag filter converter for min filter because they are the same anyway + glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, + PicaToGL::TextureMagFilterMode(min_filter)); + } else { + glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, + PicaToGL::TextureMinFilterMode(min_filter, mip_filter)); + } } if (wrap_s != config.wrap_s) { @@ -1594,6 +1614,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..c225d62b7 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -69,9 +69,16 @@ 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; + + // TODO(wwylele): remove this once mipmap for cube is implemented + bool supress_mipmap_for_cube = false; }; /// 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 491ae4370..8e6b83bc4 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -1332,6 +1332,7 @@ SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams& // 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.emplace(surface); surface = new_surface; @@ -1358,10 +1359,15 @@ 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) { + if (info.physical_address == 0) { + return nullptr; + } + SurfaceParams params; params.addr = info.physical_address; params.width = info.width; @@ -1370,23 +1376,100 @@ 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(); + if (!level_surface->invalid_regions.empty()) { + ValidateSurface(level_surface, level_surface->addr, level_surface->size); + } + 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) { @@ -1456,6 +1539,9 @@ const CachedTextureCube& RasterizerCacheOpenGL::GetTextureCube(const TextureCube for (const Face& face : faces) { if (face.watcher && !face.watcher->IsValid()) { auto surface = face.watcher->Get(); + if (!surface->invalid_regions.empty()) { + ValidateSurface(surface, surface->addr, surface->size); + } state.ResetTexture(surface->texture.handle); state.Apply(); glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, @@ -1764,6 +1850,7 @@ void RasterizerCacheOpenGL::InvalidateRegion(PAddr addr, u32 size, const Surface const auto interval = cached_surface->GetInterval() & invalid_interval; cached_surface->invalid_regions.insert(interval); + cached_surface->InvalidateAllWatcher(); // Remove only "empty" fill surfaces to avoid destroying and recreating OGL textures if (cached_surface->type == SurfaceType::Fill && diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 555e9078b..962cbceb6 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 @@ -392,6 +397,16 @@ struct CachedSurface : SurfaceParams, std::enable_shared_from_thisvalid = false; + locked->surface.reset(); + } + } + watchers.clear(); + } + private: std::list> watchers; }; @@ -434,7 +449,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/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index fdf139c3c..afb64787d 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -317,8 +317,9 @@ static std::string SampleTexture(const PicaFSConfig& config, unsigned texture_un // Only unit 0 respects the texturing type switch (state.texture0_type) { case TexturingRegs::TextureConfig::Texture2D: - return "texture(tex0, texcoord0)"; + return "textureLod(tex0, texcoord0, getLod(texcoord0 * textureSize(tex0, 0)))"; case TexturingRegs::TextureConfig::Projection2D: + // TODO (wwylele): find the exact LOD formula for projection texture return "textureProj(tex0, vec3(texcoord0, texcoord0_w))"; case TexturingRegs::TextureConfig::TextureCube: return "texture(tex_cube, vec3(texcoord0, texcoord0_w))"; @@ -335,12 +336,12 @@ static std::string SampleTexture(const PicaFSConfig& config, unsigned texture_un return "texture(tex0, texcoord0)"; } case 1: - return "texture(tex1, texcoord1)"; + return "textureLod(tex1, texcoord1, getLod(texcoord1 * textureSize(tex1, 0)))"; case 2: if (state.texture2_use_coord1) - return "texture(tex2, texcoord1)"; + return "textureLod(tex2, texcoord1, getLod(texcoord1 * textureSize(tex2, 0)))"; else - return "texture(tex2, texcoord2)"; + return "textureLod(tex2, texcoord2, getLod(texcoord2 * textureSize(tex2, 0)))"; case 3: if (state.proctex.enable) { return "ProcTex()"; @@ -1333,6 +1334,15 @@ vec4 byteround(vec4 x) { return round(x * 255.0) * (1.0 / 255.0); } +// PICA's LOD formula for 2D textures. +// This LOD formula is the same as the LOD lower limit defined in OpenGL. +// f(x, y) >= max{m_u, m_v, m_w} +// (See OpenGL 4.6 spec, 8.14.1 - Scale Factor and Level-of-Detail) +float getLod(vec2 coord) { + vec2 d = max(abs(dFdx(coord)), abs(dFdy(coord))); + return log2(max(d.x, d.y)); +} + #if ALLOW_SHADOW uvec2 DecodeShadow(uint pixel) { diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h index e923bb838..ce25f7907 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) {