Rasterizer cache refactor (#6375)
* rasterizer_cache: Remove custom texture code * It's a hacky buggy mess, will be reimplemented later when the cache is in a better state * rasterizer_cache: Refactor surface upload/download * Switch to the texture_codec header which was written as part of the vulkan backend by steveice and me * Move most of the upload logic to the rasterizer cache and out of the surface object * Scaled uploads/downloads have been disabled for now since they require more runtime infrastructure * rasterizer_cache: Refactor runtime interface * Remove aspect enum which is the same as SurfaceType * Replace Subresource with specific structures for each operation (blit/copy/clear). This mimics moderns APIs vulkan much better * Pass the surface to the runtime instead of the texture * Implement CopyTextures with glCopyImageSubData which is available on 4.3 and gles. This function also has an overload for cubes which will be removed later. * rasterizer_cache: Move texture allocation to the runtime * renderer_opengl: Remove TextureDownloaderES * It's overly compilcated and unused at the moment. Will be replaced with a simple compute shader in a later commit * rasterizer_cache: Split CachedSurface * This commit splits CachedSurface into two classes, SurfaceBase which contains the backend agnostic functions and Surface which is the opengl specific part * For now the cache uses the opengl surface directly and there are a few ugly casts with watchers, those will be taken care of when the template convertion and watcher removal are added respectively * rasterizer_cache: Move reinterpreters to the runtime * rasterizer_cache: Move some pixel format function to the cpp file * rasterizer_cache: Common texture acceleration functions * They don't contain any backend specific code so they shouldn't be duplicated * rasterizer_cache: Remove BlitSurfaces * It's better to prefer copy/blit in the caller anyway * rasterizer_cache: Only allocate needed levels * rasterizer_cache: Move texture runtime out of common dir * Also shorten the util header filename * surface_params: Cleanup code * Add more comments, organize it a bit etc * rasterizer_cache: Move texture filtering to the runtime * rasterizer_cache: Move to VideoCore * renderer_opengl: Reimplement scaled uploads/downloads * Instead of looking up for temporary textures, each allocation now contains both a scaled and unscaled handle This allows the scale operations to be done inside the surface object itself and improves performance in general * In particular the scaled download code has been expanded to use ARB_get_texture_sub_image when possible which is faster and more convenient than glReadPixels. The latter is still relevant for OpenGLES though. * Finally allocations are now given a handy debug name that can be viewed from renderdoc. * rasterizer_cache: Remove global state * gl_rasterizer: Abstract common draw operations to Framebuffer * This also allows to cache framebuffer objects instead of always swapping the textures, something that particularly benefits mali gpus * rasterizer_cache: Implement 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 * Texture cubes have also been updated to drop the watcher requirement * host_shaders: Add CMake integration for string shaders * Improves build time shader generation making it much less prone to errors. Also moves the presentation shaders here to avoid embedding them to the cpp file. * Texture filter shaders now make explicit use of uniform bindings for better vulkan compatibility * renderer_opengl: Emulate lod bias in the shader * This way opengles can emulate it correctly * gl_rasterizer: Respect GL_MAX_TEXTURE_BUFFER_SIZE * Older Bifrost Mali GPUs only support up to 64kb texture buffers. Citra would try to allocate a much larger buffer the first 64kb of which would work fine but after that the driver starts misbehaving and showing various graphical glitches * rasterizer_cache: Cleanup CopySurface * renderer_opengl: Keep frames synchronized when using a GPU debugger * rasterizer_cache: Rename Surface to SurfaceRef * Makes it clear that surface is a shared_ptr and not an object * rasterizer_cache: Cleanup * Move constructor to the top of the file * Move FindMatch to the top as well and remove the Invalid flag which was redudant; all FindMatch calls used it expect from MatchFlags::Copy which ignores it anyway * gl_texture_runtime: Make driver const * gl_texture_runtime: Fix RGB8 format handling * The texture_codec header, being written with vulkan in mind converts RGB8 to RGBA8. The backend wasn't adjusted to account for this though and treated the data as RGB8. * Also remove D16 convertions, both opengl and vulkan are required to support this format so these are not needed * gl_texture_runtime: Reduce state switches during FBO blits * glBlitFramebuffer is only affected by the scissor rectangle so just disable scissor testing instead of resetting our entire state * surface_params: Prevent texcopy that spans multiple levels * It would have failed before as well, with multi-level surfaces it triggers the assert though * renderer_opengl: Centralize texture filters * A lot of code is shared between the filters thus is makes it sense to centralize them * Also fix an issue with partial texture uploads * Address review comments * rasterizer_cache: Use leading return types * rasterizer_cache: Cleanup null checks * renderer_opengl: Add additional logging * externals: Actually downgrade glad * For some reason I missed adding the files to git * surface_params: Do not check for levels in exact match * Some games will try to use the base level of a multi level surface. Checking for levels forces another surface to be created and a copy to be made which is both unncessary and breaks custom textures --------- Co-authored-by: bunnei <bunneidev@gmail.com>
This commit is contained in:
60
src/video_core/host_shaders/CMakeLists.txt
Normal file
60
src/video_core/host_shaders/CMakeLists.txt
Normal file
@@ -0,0 +1,60 @@
|
||||
# Copyright 2023 Citra Emulator Project
|
||||
# Licensed under GPLv2 or any later version
|
||||
# Refer to the license.txt file included.
|
||||
|
||||
set(SHADER_FILES
|
||||
format_reinterpreter/d24s8_to_rgba8.frag
|
||||
format_reinterpreter/fullscreen_quad.vert
|
||||
format_reinterpreter/rgba4_to_rgb5a1.frag
|
||||
texture_filtering/bicubic.frag
|
||||
texture_filtering/nearest_neighbor.frag
|
||||
texture_filtering/refine.frag
|
||||
texture_filtering/scale_force.frag
|
||||
texture_filtering/tex_coord.vert
|
||||
texture_filtering/xbrz_freescale.frag
|
||||
texture_filtering/x_gradient.frag
|
||||
texture_filtering/y_gradient.frag
|
||||
full_screen_triangle.vert
|
||||
opengl_present.frag
|
||||
opengl_present.vert
|
||||
opengl_present_anaglyph.frag
|
||||
opengl_present_interlaced.frag
|
||||
)
|
||||
|
||||
set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
|
||||
set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders)
|
||||
set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
|
||||
|
||||
set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in)
|
||||
set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake)
|
||||
|
||||
foreach(FILENAME IN ITEMS ${SHADER_FILES})
|
||||
string(REPLACE "." "_" SHADER_NAME ${FILENAME})
|
||||
set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME})
|
||||
# Skip generating source headers on Vulkan exclusive files
|
||||
if (NOT ${FILENAME} MATCHES "vulkan.*")
|
||||
set(SOURCE_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h)
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${SOURCE_HEADER_FILE}
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${SOURCE_HEADER_FILE} ${INPUT_FILE}
|
||||
MAIN_DEPENDENCY
|
||||
${SOURCE_FILE}
|
||||
DEPENDS
|
||||
${INPUT_FILE}
|
||||
# HEADER_GENERATOR should be included here but msbuild seems to assume it's always modified
|
||||
)
|
||||
set(SHADER_HEADERS ${SHADER_HEADERS} ${SOURCE_HEADER_FILE})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(SHADER_SOURCES ${SHADER_FILES})
|
||||
list(APPEND SHADER_SOURCES ${GLSL_INCLUDES})
|
||||
|
||||
add_custom_target(host_shaders
|
||||
DEPENDS
|
||||
${SHADER_HEADERS}
|
||||
SOURCES
|
||||
${SHADER_SOURCES}
|
||||
)
|
||||
36
src/video_core/host_shaders/StringShaderHeader.cmake
Normal file
36
src/video_core/host_shaders/StringShaderHeader.cmake
Normal file
@@ -0,0 +1,36 @@
|
||||
# SPDX-FileCopyrightText: 2020 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(SOURCE_FILE ${CMAKE_ARGV3})
|
||||
set(HEADER_FILE ${CMAKE_ARGV4})
|
||||
set(INPUT_FILE ${CMAKE_ARGV5})
|
||||
|
||||
get_filename_component(CONTENTS_NAME ${SOURCE_FILE} NAME)
|
||||
string(REPLACE "." "_" CONTENTS_NAME ${CONTENTS_NAME})
|
||||
string(TOUPPER ${CONTENTS_NAME} CONTENTS_NAME)
|
||||
|
||||
FILE(READ ${SOURCE_FILE} line_contents)
|
||||
|
||||
# Replace double quotes with single quotes,
|
||||
# as double quotes will be used to wrap the lines
|
||||
STRING(REGEX REPLACE "\"" "'" line_contents "${line_contents}")
|
||||
|
||||
# CMake separates list elements with semicolons, but semicolons
|
||||
# are used extensively in the shader code.
|
||||
# Replace with a temporary marker, to be reverted later.
|
||||
STRING(REGEX REPLACE ";" "{{SEMICOLON}}" line_contents "${line_contents}")
|
||||
|
||||
# Make every line an individual element in the CMake list.
|
||||
STRING(REGEX REPLACE "\n" ";" line_contents "${line_contents}")
|
||||
|
||||
# Build the shader string, wrapping each line in double quotes.
|
||||
foreach(line IN LISTS line_contents)
|
||||
string(CONCAT CONTENTS "${CONTENTS}" \"${line}\\n\"\n)
|
||||
endforeach()
|
||||
|
||||
# Revert the original semicolons in the source.
|
||||
STRING(REGEX REPLACE "{{SEMICOLON}}" ";" CONTENTS "${CONTENTS}")
|
||||
|
||||
get_filename_component(OUTPUT_DIR ${HEADER_FILE} DIRECTORY)
|
||||
make_directory(${OUTPUT_DIR})
|
||||
configure_file(${INPUT_FILE} ${HEADER_FILE} @ONLY)
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
|
||||
layout(location = 0) in mediump vec2 dst_coord;
|
||||
layout(location = 0) out lowp vec4 frag_color;
|
||||
|
||||
layout(binding = 0) uniform highp sampler2D depth;
|
||||
layout(binding = 1) uniform lowp usampler2D stencil;
|
||||
uniform mediump ivec2 dst_size;
|
||||
uniform mediump ivec2 src_size;
|
||||
uniform mediump ivec2 src_offset;
|
||||
|
||||
void main() {
|
||||
mediump ivec2 tex_coord;
|
||||
if (src_size == dst_size) {
|
||||
tex_coord = ivec2(dst_coord);
|
||||
} else {
|
||||
highp int tex_index = int(dst_coord.y) * dst_size.x + int(dst_coord.x);
|
||||
mediump int y = tex_index / src_size.x;
|
||||
tex_coord = ivec2(tex_index - y * src_size.x, y);
|
||||
}
|
||||
tex_coord -= src_offset;
|
||||
|
||||
highp uint depth_val =
|
||||
uint(texelFetch(depth, tex_coord, 0).x * (exp2(32.0) - 1.0));
|
||||
lowp uint stencil_val = texelFetch(stencil, tex_coord, 0).x;
|
||||
highp uvec4 components =
|
||||
uvec4(stencil_val, (uvec3(depth_val) >> uvec3(24u, 16u, 8u)) & 0x000000FFu);
|
||||
frag_color = vec4(components) / (exp2(8.0) - 1.0);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
|
||||
layout(location = 0) out vec2 dst_coord;
|
||||
|
||||
uniform mediump ivec2 dst_size;
|
||||
|
||||
const vec2 vertices[4] =
|
||||
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
|
||||
dst_coord = (vertices[gl_VertexID] / 2.0 + 0.5) * vec2(dst_size);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
|
||||
layout(location = 0) in mediump vec2 dst_coord;
|
||||
layout(location = 0) out lowp vec4 frag_color;
|
||||
|
||||
layout(binding = 0) uniform lowp sampler2D source;
|
||||
uniform mediump ivec2 dst_size;
|
||||
uniform mediump ivec2 src_size;
|
||||
uniform mediump ivec2 src_offset;
|
||||
|
||||
void main() {
|
||||
mediump ivec2 tex_coord;
|
||||
if (src_size == dst_size) {
|
||||
tex_coord = ivec2(dst_coord);
|
||||
} else {
|
||||
highp int tex_index = int(dst_coord.y) * dst_size.x + int(dst_coord.x);
|
||||
mediump int y = tex_index / src_size.x;
|
||||
tex_coord = ivec2(tex_index - y * src_size.x, y);
|
||||
}
|
||||
tex_coord -= src_offset;
|
||||
|
||||
lowp ivec4 rgba4 = ivec4(texelFetch(source, tex_coord, 0) * (exp2(4.0) - 1.0));
|
||||
lowp ivec3 rgb5 =
|
||||
((rgba4.rgb << ivec3(1, 2, 3)) | (rgba4.gba >> ivec3(3, 2, 1))) & 0x1F;
|
||||
frag_color = vec4(vec3(rgb5) / (exp2(5.0) - 1.0), rgba4.a & 0x01);
|
||||
}
|
||||
20
src/video_core/host_shaders/full_screen_triangle.vert
Normal file
20
src/video_core/host_shaders/full_screen_triangle.vert
Normal file
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
//? #version 450
|
||||
|
||||
out gl_PerVertex {
|
||||
vec4 gl_Position;
|
||||
};
|
||||
|
||||
layout(location = 0) out vec2 texcoord;
|
||||
|
||||
layout (location = 0) uniform vec2 tex_scale;
|
||||
layout (location = 1) uniform vec2 tex_offset;
|
||||
|
||||
void main() {
|
||||
float x = float((gl_VertexID & 1) << 2);
|
||||
float y = float((gl_VertexID & 2) << 1);
|
||||
gl_Position = vec4(x - 1.0, y - 1.0, 0.0, 1.0);
|
||||
texcoord = fma(vec2(x, y) / 2.0, tex_scale, tex_offset);
|
||||
}
|
||||
18
src/video_core/host_shaders/opengl_present.frag
Normal file
18
src/video_core/host_shaders/opengl_present.frag
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
|
||||
layout(location = 0) in vec2 frag_tex_coord;
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D color_texture;
|
||||
|
||||
uniform vec4 i_resolution;
|
||||
uniform vec4 o_resolution;
|
||||
uniform int layer;
|
||||
|
||||
void main() {
|
||||
color = texture(color_texture, frag_tex_coord);
|
||||
}
|
||||
23
src/video_core/host_shaders/opengl_present.vert
Normal file
23
src/video_core/host_shaders/opengl_present.vert
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
layout(location = 0) in vec2 vert_position;
|
||||
layout(location = 1) in vec2 vert_tex_coord;
|
||||
layout(location = 0) out vec2 frag_tex_coord;
|
||||
|
||||
// This is a truncated 3x3 matrix for 2D transformations:
|
||||
// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
|
||||
// The third column performs translation.
|
||||
// The third row could be used for projection, which we don't need in 2D. It hence is assumed to
|
||||
// implicitly be [0, 0, 1]
|
||||
uniform mat3x2 modelview_matrix;
|
||||
|
||||
void main() {
|
||||
// Multiply input position by the rotscale part of the matrix and then manually translate by
|
||||
// the last column. This is equivalent to using a full 3x3 matrix and expanding the vector
|
||||
// to `vec3(vert_position.xy, 1.0)`
|
||||
gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0);
|
||||
frag_tex_coord = vert_tex_coord;
|
||||
}
|
||||
32
src/video_core/host_shaders/opengl_present_anaglyph.frag
Normal file
32
src/video_core/host_shaders/opengl_present_anaglyph.frag
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
|
||||
// Anaglyph Red-Cyan shader based on Dubois algorithm
|
||||
// Constants taken from the paper:
|
||||
// "Conversion of a Stereo Pair to Anaglyph with
|
||||
// the Least-Squares Projection Method"
|
||||
// Eric Dubois, March 2009
|
||||
const mat3 l = mat3( 0.437, 0.449, 0.164,
|
||||
-0.062,-0.062,-0.024,
|
||||
-0.048,-0.050,-0.017);
|
||||
const mat3 r = mat3(-0.011,-0.032,-0.007,
|
||||
0.377, 0.761, 0.009,
|
||||
-0.026,-0.093, 1.234);
|
||||
|
||||
layout(location = 0) in vec2 frag_tex_coord;
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D color_texture;
|
||||
layout(binding = 1) uniform sampler2D color_texture_r;
|
||||
|
||||
uniform vec4 resolution;
|
||||
uniform int layer;
|
||||
|
||||
void main() {
|
||||
vec4 color_tex_l = texture(color_texture, frag_tex_coord);
|
||||
vec4 color_tex_r = texture(color_texture_r, frag_tex_coord);
|
||||
color = vec4(color_tex_l.rgb*l+color_tex_r.rgb*r, color_tex_l.a);
|
||||
}
|
||||
22
src/video_core/host_shaders/opengl_present_interlaced.frag
Normal file
22
src/video_core/host_shaders/opengl_present_interlaced.frag
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
|
||||
layout(location = 0) in vec2 frag_tex_coord;
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D color_texture;
|
||||
layout(binding = 1) uniform sampler2D color_texture_r;
|
||||
|
||||
uniform vec4 o_resolution;
|
||||
uniform int reverse_interlaced;
|
||||
|
||||
void main() {
|
||||
float screen_row = o_resolution.x * frag_tex_coord.x;
|
||||
if (int(screen_row) % 2 == reverse_interlaced)
|
||||
color = texture(color_texture, frag_tex_coord);
|
||||
else
|
||||
color = texture(color_texture_r, frag_tex_coord);
|
||||
}
|
||||
15
src/video_core/host_shaders/source_shader.h.in
Normal file
15
src/video_core/host_shaders/source_shader.h.in
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace HostShaders {
|
||||
|
||||
constexpr std::string_view @CONTENTS_NAME@ = {
|
||||
@CONTENTS@
|
||||
};
|
||||
|
||||
} // namespace HostShaders
|
||||
56
src/video_core/host_shaders/texture_filtering/bicubic.frag
Normal file
56
src/video_core/host_shaders/texture_filtering/bicubic.frag
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 330
|
||||
precision mediump float;
|
||||
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D input_texture;
|
||||
|
||||
// from http://www.java-gaming.org/index.php?topic=35123.0
|
||||
vec4 cubic(float v) {
|
||||
vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
|
||||
vec4 s = n * n * n;
|
||||
float x = s.x;
|
||||
float y = s.y - 4.0 * s.x;
|
||||
float z = s.z - 4.0 * s.y + 6.0 * s.x;
|
||||
float w = 6.0 - x - y - z;
|
||||
return vec4(x, y, z, w) * (1.0 / 6.0);
|
||||
}
|
||||
|
||||
vec4 textureBicubic(sampler2D tex_sampler, vec2 texCoords) {
|
||||
vec2 texSize = vec2(textureSize(tex_sampler, 0));
|
||||
vec2 invTexSize = 1.0 / texSize;
|
||||
|
||||
texCoords = texCoords * texSize - 0.5;
|
||||
|
||||
vec2 fxy = fract(texCoords);
|
||||
texCoords -= fxy;
|
||||
|
||||
vec4 xcubic = cubic(fxy.x);
|
||||
vec4 ycubic = cubic(fxy.y);
|
||||
|
||||
vec4 c = texCoords.xxyy + vec2(-0.5, +1.5).xyxy;
|
||||
|
||||
vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
|
||||
vec4 offset = c + vec4(xcubic.yw, ycubic.yw) / s;
|
||||
|
||||
offset *= invTexSize.xxyy;
|
||||
|
||||
vec4 sample0 = texture(tex_sampler, offset.xz);
|
||||
vec4 sample1 = texture(tex_sampler, offset.yz);
|
||||
vec4 sample2 = texture(tex_sampler, offset.xw);
|
||||
vec4 sample3 = texture(tex_sampler, offset.yw);
|
||||
|
||||
float sx = s.x / (s.x + s.y);
|
||||
float sy = s.z / (s.z + s.w);
|
||||
|
||||
return mix(mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy);
|
||||
}
|
||||
|
||||
void main() {
|
||||
frag_color = textureBicubic(input_texture, tex_coord);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
precision mediump float;
|
||||
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D input_texture;
|
||||
|
||||
void main() {
|
||||
frag_color = texture(input_texture, tex_coord);
|
||||
}
|
||||
138
src/video_core/host_shaders/texture_filtering/refine.frag
Normal file
138
src/video_core/host_shaders/texture_filtering/refine.frag
Normal file
@@ -0,0 +1,138 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2019 bloc97
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//? #version 430 core
|
||||
precision mediump float;
|
||||
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D HOOKED;
|
||||
layout(binding = 1) uniform sampler2D LUMAD;
|
||||
|
||||
const float LINE_DETECT_THRESHOLD = 0.4;
|
||||
const float STRENGTH = 0.6;
|
||||
|
||||
// the original shader used the alpha channel for luminance,
|
||||
// which doesn't work for our use case
|
||||
struct RGBAL {
|
||||
vec4 c;
|
||||
float l;
|
||||
};
|
||||
|
||||
vec4 getAverage(vec4 cc, vec4 a, vec4 b, vec4 c) {
|
||||
return cc * (1.0 - STRENGTH) + ((a + b + c) / 3.0) * STRENGTH;
|
||||
}
|
||||
|
||||
#define GetRGBAL(x_offset, y_offset) \
|
||||
RGBAL(textureLodOffset(HOOKED, tex_coord, 0.0, ivec2(x_offset, y_offset)), \
|
||||
textureLodOffset(LUMAD, tex_coord, 0.0, ivec2(x_offset, y_offset)).x)
|
||||
|
||||
float min3v(float a, float b, float c) {
|
||||
return min(min(a, b), c);
|
||||
}
|
||||
|
||||
float max3v(float a, float b, float c) {
|
||||
return max(max(a, b), c);
|
||||
}
|
||||
|
||||
vec4 Compute() {
|
||||
RGBAL cc = GetRGBAL(0, 0);
|
||||
|
||||
if (cc.l > LINE_DETECT_THRESHOLD) {
|
||||
return cc.c;
|
||||
}
|
||||
|
||||
RGBAL tl = GetRGBAL(-1, -1);
|
||||
RGBAL t = GetRGBAL(0, -1);
|
||||
RGBAL tr = GetRGBAL(1, -1);
|
||||
|
||||
RGBAL l = GetRGBAL(-1, 0);
|
||||
|
||||
RGBAL r = GetRGBAL(1, 0);
|
||||
|
||||
RGBAL bl = GetRGBAL(-1, 1);
|
||||
RGBAL b = GetRGBAL(0, 1);
|
||||
RGBAL br = GetRGBAL(1, 1);
|
||||
|
||||
// Kernel 0 and 4
|
||||
float maxDark = max3v(br.l, b.l, bl.l);
|
||||
float minLight = min3v(tl.l, t.l, tr.l);
|
||||
|
||||
if (minLight > cc.l && minLight > maxDark) {
|
||||
return getAverage(cc.c, tl.c, t.c, tr.c);
|
||||
} else {
|
||||
maxDark = max3v(tl.l, t.l, tr.l);
|
||||
minLight = min3v(br.l, b.l, bl.l);
|
||||
if (minLight > cc.l && minLight > maxDark) {
|
||||
return getAverage(cc.c, br.c, b.c, bl.c);
|
||||
}
|
||||
}
|
||||
|
||||
// Kernel 1 and 5
|
||||
maxDark = max3v(cc.l, l.l, b.l);
|
||||
minLight = min3v(r.l, t.l, tr.l);
|
||||
|
||||
if (minLight > maxDark) {
|
||||
return getAverage(cc.c, r.c, t.c, tr.c);
|
||||
} else {
|
||||
maxDark = max3v(cc.l, r.l, t.l);
|
||||
minLight = min3v(bl.l, l.l, b.l);
|
||||
if (minLight > maxDark) {
|
||||
return getAverage(cc.c, bl.c, l.c, b.c);
|
||||
}
|
||||
}
|
||||
|
||||
// Kernel 2 and 6
|
||||
maxDark = max3v(l.l, tl.l, bl.l);
|
||||
minLight = min3v(r.l, br.l, tr.l);
|
||||
|
||||
if (minLight > cc.l && minLight > maxDark) {
|
||||
return getAverage(cc.c, r.c, br.c, tr.c);
|
||||
} else {
|
||||
maxDark = max3v(r.l, br.l, tr.l);
|
||||
minLight = min3v(l.l, tl.l, bl.l);
|
||||
if (minLight > cc.l && minLight > maxDark) {
|
||||
return getAverage(cc.c, l.c, tl.c, bl.c);
|
||||
}
|
||||
}
|
||||
|
||||
// Kernel 3 and 7
|
||||
maxDark = max3v(cc.l, l.l, t.l);
|
||||
minLight = min3v(r.l, br.l, b.l);
|
||||
|
||||
if (minLight > maxDark) {
|
||||
return getAverage(cc.c, r.c, br.c, b.c);
|
||||
} else {
|
||||
maxDark = max3v(cc.l, r.l, b.l);
|
||||
minLight = min3v(t.l, l.l, tl.l);
|
||||
if (minLight > maxDark) {
|
||||
return getAverage(cc.c, t.c, l.c, tl.c);
|
||||
}
|
||||
}
|
||||
|
||||
return cc.c;
|
||||
}
|
||||
|
||||
void main() {
|
||||
frag_color = Compute();
|
||||
}
|
||||
136
src/video_core/host_shaders/texture_filtering/scale_force.frag
Normal file
136
src/video_core/host_shaders/texture_filtering/scale_force.frag
Normal file
@@ -0,0 +1,136 @@
|
||||
// from https://github.com/BreadFish64/ScaleFish/tree/master/scale_force
|
||||
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2020 BreadFish64
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//? #version 320 es
|
||||
|
||||
precision mediump float;
|
||||
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D input_texture;
|
||||
|
||||
vec2 tex_size;
|
||||
vec2 inv_tex_size;
|
||||
|
||||
vec4 cubic(float v) {
|
||||
vec3 n = vec3(1.0, 2.0, 3.0) - v;
|
||||
vec3 s = n * n * n;
|
||||
float x = s.x;
|
||||
float y = s.y - 4.0 * s.x;
|
||||
float z = s.z - 4.0 * s.y + 6.0 * s.x;
|
||||
float w = 6.0 - x - y - z;
|
||||
return vec4(x, y, z, w) / 6.0;
|
||||
}
|
||||
|
||||
// Bicubic interpolation
|
||||
vec4 textureBicubic(vec2 tex_coords) {
|
||||
tex_coords = tex_coords * tex_size - 0.5;
|
||||
|
||||
vec2 fxy = modf(tex_coords, tex_coords);
|
||||
|
||||
vec4 xcubic = cubic(fxy.x);
|
||||
vec4 ycubic = cubic(fxy.y);
|
||||
|
||||
vec4 c = tex_coords.xxyy + vec2(-0.5, +1.5).xyxy;
|
||||
|
||||
vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
|
||||
vec4 offset = c + vec4(xcubic.yw, ycubic.yw) / s;
|
||||
|
||||
offset *= inv_tex_size.xxyy;
|
||||
|
||||
vec4 sample0 = textureLod(input_texture, offset.xz, 0.0);
|
||||
vec4 sample1 = textureLod(input_texture, offset.yz, 0.0);
|
||||
vec4 sample2 = textureLod(input_texture, offset.xw, 0.0);
|
||||
vec4 sample3 = textureLod(input_texture, offset.yw, 0.0);
|
||||
|
||||
float sx = s.x / (s.x + s.y);
|
||||
float sy = s.z / (s.z + s.w);
|
||||
|
||||
return mix(mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy);
|
||||
}
|
||||
|
||||
mat4x3 center_matrix;
|
||||
vec4 center_alpha;
|
||||
|
||||
// Finds the distance between four colors and cc in YCbCr space
|
||||
vec4 ColorDist(vec4 A, vec4 B, vec4 C, vec4 D) {
|
||||
// https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.2020_conversion
|
||||
const vec3 K = vec3(0.2627, 0.6780, 0.0593);
|
||||
const float LUMINANCE_WEIGHT = .6;
|
||||
const mat3 YCBCR_MATRIX =
|
||||
mat3(K * LUMINANCE_WEIGHT, -.5 * K.r / (1.0 - K.b), -.5 * K.g / (1.0 - K.b), .5, .5,
|
||||
-.5 * K.g / (1.0 - K.r), -.5 * K.b / (1.0 - K.r));
|
||||
|
||||
mat4x3 colors = mat4x3(A.rgb, B.rgb, C.rgb, D.rgb) - center_matrix;
|
||||
mat4x3 YCbCr = YCBCR_MATRIX * colors;
|
||||
vec4 color_dist = vec3(1.0) * YCbCr;
|
||||
color_dist *= color_dist;
|
||||
vec4 alpha = vec4(A.a, B.a, C.a, D.a);
|
||||
|
||||
return sqrt((color_dist + abs(center_alpha - alpha)) * alpha * center_alpha);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 bl = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(-1, -1));
|
||||
vec4 bc = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(0, -1));
|
||||
vec4 br = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(1, -1));
|
||||
vec4 cl = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(-1, 0));
|
||||
vec4 cc = textureLod(input_texture, tex_coord, 0.0);
|
||||
vec4 cr = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(1, 0));
|
||||
vec4 tl = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(-1, 1));
|
||||
vec4 tc = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(0, 1));
|
||||
vec4 tr = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(1, 1));
|
||||
|
||||
|
||||
tex_size = vec2(textureSize(input_texture, 0));
|
||||
inv_tex_size = 1.0 / tex_size;
|
||||
center_matrix = mat4x3(cc.rgb, cc.rgb, cc.rgb, cc.rgb);
|
||||
center_alpha = cc.aaaa;
|
||||
|
||||
vec4 offset_tl = ColorDist(tl, tc, tr, cr);
|
||||
vec4 offset_br = ColorDist(br, bc, bl, cl);
|
||||
|
||||
// Calculate how different cc is from the texels around it
|
||||
float total_dist = dot(offset_tl + offset_br, vec4(1.0));
|
||||
|
||||
// Add together all the distances with direction taken into account
|
||||
vec4 tmp = offset_tl - offset_br;
|
||||
vec2 total_offset = tmp.wy + tmp.zz + vec2(-tmp.x, tmp.x);
|
||||
|
||||
if (total_dist == 0.0) {
|
||||
// Doing bicubic filtering just past the edges where the offset is 0 causes black floaters
|
||||
// and it doesn't really matter which filter is used when the colors aren't changing.
|
||||
frag_color = cc;
|
||||
} else {
|
||||
// When the image has thin points, they tend to split apart.
|
||||
// This is because the texels all around are different
|
||||
// and total_offset reaches into clear areas.
|
||||
// This works pretty well to keep the offset in bounds for these cases.
|
||||
float clamp_val = length(total_offset) / total_dist;
|
||||
vec2 final_offset = clamp(total_offset, -clamp_val, clamp_val) * inv_tex_size;
|
||||
|
||||
frag_color = textureBicubic(tex_coord - final_offset);
|
||||
}
|
||||
}
|
||||
14
src/video_core/host_shaders/texture_filtering/tex_coord.vert
Normal file
14
src/video_core/host_shaders/texture_filtering/tex_coord.vert
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
layout(location = 0) out vec2 tex_coord;
|
||||
|
||||
const vec2 vertices[4] =
|
||||
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
|
||||
tex_coord = (vertices[gl_VertexID] + 1.0) / 2.0;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2019 bloc97
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//? #version 430 core
|
||||
precision mediump float;
|
||||
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 0) out vec2 frag_color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D tex_input;
|
||||
|
||||
const vec3 K = vec3(0.2627, 0.6780, 0.0593);
|
||||
// TODO: improve handling of alpha channel
|
||||
#define GetLum(xoffset) dot(K, textureLodOffset(tex_input, tex_coord, 0.0, ivec2(xoffset, 0)).rgb)
|
||||
|
||||
void main() {
|
||||
float l = GetLum(-1);
|
||||
float c = GetLum(0);
|
||||
float r = GetLum(1);
|
||||
|
||||
frag_color = vec2(r - l, l + 2.0 * c + r);
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
precision mediump float;
|
||||
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D tex;
|
||||
layout(location = 2) uniform lowp float scale;
|
||||
|
||||
const int BLEND_NONE = 0;
|
||||
const int BLEND_NORMAL = 1;
|
||||
const int BLEND_DOMINANT = 2;
|
||||
const float LUMINANCE_WEIGHT = 1.0;
|
||||
const float EQUAL_COLOR_TOLERANCE = 30.0 / 255.0;
|
||||
const float STEEP_DIRECTION_THRESHOLD = 2.2;
|
||||
const float DOMINANT_DIRECTION_THRESHOLD = 3.6;
|
||||
|
||||
float ColorDist(vec4 a, vec4 b) {
|
||||
// https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.2020_conversion
|
||||
const vec3 K = vec3(0.2627, 0.6780, 0.0593);
|
||||
const mat3 MATRIX = mat3(K, -.5 * K.r / (1.0 - K.b), -.5 * K.g / (1.0 - K.b), .5, .5,
|
||||
-.5 * K.g / (1.0 - K.r), -.5 * K.b / (1.0 - K.r));
|
||||
vec4 diff = a - b;
|
||||
vec3 YCbCr = diff.rgb * MATRIX;
|
||||
// LUMINANCE_WEIGHT is currently 1, otherwise y would be multiplied by it
|
||||
float d = length(YCbCr);
|
||||
return sqrt(a.a * b.a * d * d + diff.a * diff.a);
|
||||
}
|
||||
|
||||
bool IsPixEqual(const vec4 pixA, const vec4 pixB) {
|
||||
return ColorDist(pixA, pixB) < EQUAL_COLOR_TOLERANCE;
|
||||
}
|
||||
|
||||
float GetLeftRatio(vec2 center, vec2 origin, vec2 direction) {
|
||||
vec2 P0 = center - origin;
|
||||
vec2 proj = direction * (dot(P0, direction) / dot(direction, direction));
|
||||
vec2 distv = P0 - proj;
|
||||
vec2 orth = vec2(-direction.y, direction.x);
|
||||
float side = sign(dot(P0, orth));
|
||||
float v = side * length(distv * scale);
|
||||
return smoothstep(-sqrt(2.0) / 2.0, sqrt(2.0) / 2.0, v);
|
||||
}
|
||||
|
||||
#define P(x, y) textureOffset(tex, coord, ivec2(x, y))
|
||||
|
||||
void main() {
|
||||
vec2 source_size = vec2(textureSize(tex, 0));
|
||||
vec2 pos = fract(tex_coord * source_size) - vec2(0.5, 0.5);
|
||||
vec2 coord = tex_coord - pos / source_size;
|
||||
|
||||
//---------------------------------------
|
||||
// Input Pixel Mapping: -|x|x|x|-
|
||||
// x|A|B|C|x
|
||||
// x|D|E|F|x
|
||||
// x|G|H|I|x
|
||||
// -|x|x|x|-
|
||||
vec4 A = P(-1, -1);
|
||||
vec4 B = P(0, -1);
|
||||
vec4 C = P(1, -1);
|
||||
vec4 D = P(-1, 0);
|
||||
vec4 E = P(0, 0);
|
||||
vec4 F = P(1, 0);
|
||||
vec4 G = P(-1, 1);
|
||||
vec4 H = P(0, 1);
|
||||
vec4 I = P(1, 1);
|
||||
// blendResult Mapping: x|y|
|
||||
// w|z|
|
||||
ivec4 blendResult = ivec4(BLEND_NONE, BLEND_NONE, BLEND_NONE, BLEND_NONE);
|
||||
// Preprocess corners
|
||||
// Pixel Tap Mapping: -|-|-|-|-
|
||||
// -|-|B|C|-
|
||||
// -|D|E|F|x
|
||||
// -|G|H|I|x
|
||||
// -|-|x|x|-
|
||||
if (!((E == F && H == I) || (E == H && F == I))) {
|
||||
float dist_H_F = ColorDist(G, E) + ColorDist(E, C) + ColorDist(P(0, 2), I) +
|
||||
ColorDist(I, P(2, 0)) + (4.0 * ColorDist(H, F));
|
||||
float dist_E_I = ColorDist(D, H) + ColorDist(H, P(1, 2)) + ColorDist(B, F) +
|
||||
ColorDist(F, P(2, 1)) + (4.0 * ColorDist(E, I));
|
||||
bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_H_F) < dist_E_I;
|
||||
blendResult.z = ((dist_H_F < dist_E_I) && E != F && E != H)
|
||||
? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL)
|
||||
: BLEND_NONE;
|
||||
}
|
||||
// Pixel Tap Mapping: -|-|-|-|-
|
||||
// -|A|B|-|-
|
||||
// x|D|E|F|-
|
||||
// x|G|H|I|-
|
||||
// -|x|x|-|-
|
||||
if (!((D == E && G == H) || (D == G && E == H))) {
|
||||
float dist_G_E = ColorDist(P(-2, 1), D) + ColorDist(D, B) + ColorDist(P(-1, 2), H) +
|
||||
ColorDist(H, F) + (4.0 * ColorDist(G, E));
|
||||
float dist_D_H = ColorDist(P(-2, 0), G) + ColorDist(G, P(0, 2)) + ColorDist(A, E) +
|
||||
ColorDist(E, I) + (4.0 * ColorDist(D, H));
|
||||
bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_D_H) < dist_G_E;
|
||||
blendResult.w = ((dist_G_E > dist_D_H) && E != D && E != H)
|
||||
? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL)
|
||||
: BLEND_NONE;
|
||||
}
|
||||
// Pixel Tap Mapping: -|-|x|x|-
|
||||
// -|A|B|C|x
|
||||
// -|D|E|F|x
|
||||
// -|-|H|I|-
|
||||
// -|-|-|-|-
|
||||
if (!((B == C && E == F) || (B == E && C == F))) {
|
||||
float dist_E_C = ColorDist(D, B) + ColorDist(B, P(1, -2)) + ColorDist(H, F) +
|
||||
ColorDist(F, P(2, -1)) + (4.0 * ColorDist(E, C));
|
||||
float dist_B_F = ColorDist(A, E) + ColorDist(E, I) + ColorDist(P(0, -2), C) +
|
||||
ColorDist(C, P(2, 0)) + (4.0 * ColorDist(B, F));
|
||||
bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_B_F) < dist_E_C;
|
||||
blendResult.y = ((dist_E_C > dist_B_F) && E != B && E != F)
|
||||
? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL)
|
||||
: BLEND_NONE;
|
||||
}
|
||||
// Pixel Tap Mapping: -|x|x|-|-
|
||||
// x|A|B|C|-
|
||||
// x|D|E|F|-
|
||||
// -|G|H|-|-
|
||||
// -|-|-|-|-
|
||||
if (!((A == B && D == E) || (A == D && B == E))) {
|
||||
float dist_D_B = ColorDist(P(-2, 0), A) + ColorDist(A, P(0, -2)) + ColorDist(G, E) +
|
||||
ColorDist(E, C) + (4.0 * ColorDist(D, B));
|
||||
float dist_A_E = ColorDist(P(-2, -1), D) + ColorDist(D, H) + ColorDist(P(-1, -2), B) +
|
||||
ColorDist(B, F) + (4.0 * ColorDist(A, E));
|
||||
bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_D_B) < dist_A_E;
|
||||
blendResult.x = ((dist_D_B < dist_A_E) && E != D && E != B)
|
||||
? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL)
|
||||
: BLEND_NONE;
|
||||
}
|
||||
vec4 res = E;
|
||||
// Pixel Tap Mapping: -|-|-|-|-
|
||||
// -|-|B|C|-
|
||||
// -|D|E|F|x
|
||||
// -|G|H|I|x
|
||||
// -|-|x|x|-
|
||||
if (blendResult.z != BLEND_NONE) {
|
||||
float dist_F_G = ColorDist(F, G);
|
||||
float dist_H_C = ColorDist(H, C);
|
||||
bool doLineBlend = (blendResult.z == BLEND_DOMINANT ||
|
||||
!((blendResult.y != BLEND_NONE && !IsPixEqual(E, G)) ||
|
||||
(blendResult.w != BLEND_NONE && !IsPixEqual(E, C)) ||
|
||||
(IsPixEqual(G, H) && IsPixEqual(H, I) && IsPixEqual(I, F) &&
|
||||
IsPixEqual(F, C) && !IsPixEqual(E, I))));
|
||||
vec2 origin = vec2(0.0, 1.0 / sqrt(2.0));
|
||||
vec2 direction = vec2(1.0, -1.0);
|
||||
if (doLineBlend) {
|
||||
bool haveShallowLine =
|
||||
(STEEP_DIRECTION_THRESHOLD * dist_F_G <= dist_H_C) && E != G && D != G;
|
||||
bool haveSteepLine =
|
||||
(STEEP_DIRECTION_THRESHOLD * dist_H_C <= dist_F_G) && E != C && B != C;
|
||||
origin = haveShallowLine ? vec2(0.0, 0.25) : vec2(0.0, 0.5);
|
||||
direction.x += haveShallowLine ? 1.0 : 0.0;
|
||||
direction.y -= haveSteepLine ? 1.0 : 0.0;
|
||||
}
|
||||
vec4 blendPix = mix(H, F, step(ColorDist(E, F), ColorDist(E, H)));
|
||||
res = mix(res, blendPix, GetLeftRatio(pos, origin, direction));
|
||||
}
|
||||
// Pixel Tap Mapping: -|-|-|-|-
|
||||
// -|A|B|-|-
|
||||
// x|D|E|F|-
|
||||
// x|G|H|I|-
|
||||
// -|x|x|-|-
|
||||
if (blendResult.w != BLEND_NONE) {
|
||||
float dist_H_A = ColorDist(H, A);
|
||||
float dist_D_I = ColorDist(D, I);
|
||||
bool doLineBlend = (blendResult.w == BLEND_DOMINANT ||
|
||||
!((blendResult.z != BLEND_NONE && !IsPixEqual(E, A)) ||
|
||||
(blendResult.x != BLEND_NONE && !IsPixEqual(E, I)) ||
|
||||
(IsPixEqual(A, D) && IsPixEqual(D, G) && IsPixEqual(G, H) &&
|
||||
IsPixEqual(H, I) && !IsPixEqual(E, G))));
|
||||
vec2 origin = vec2(-1.0 / sqrt(2.0), 0.0);
|
||||
vec2 direction = vec2(1.0, 1.0);
|
||||
if (doLineBlend) {
|
||||
bool haveShallowLine =
|
||||
(STEEP_DIRECTION_THRESHOLD * dist_H_A <= dist_D_I) && E != A && B != A;
|
||||
bool haveSteepLine =
|
||||
(STEEP_DIRECTION_THRESHOLD * dist_D_I <= dist_H_A) && E != I && F != I;
|
||||
origin = haveShallowLine ? vec2(-0.25, 0.0) : vec2(-0.5, 0.0);
|
||||
direction.y += haveShallowLine ? 1.0 : 0.0;
|
||||
direction.x += haveSteepLine ? 1.0 : 0.0;
|
||||
}
|
||||
origin = origin;
|
||||
direction = direction;
|
||||
vec4 blendPix = mix(H, D, step(ColorDist(E, D), ColorDist(E, H)));
|
||||
res = mix(res, blendPix, GetLeftRatio(pos, origin, direction));
|
||||
}
|
||||
// Pixel Tap Mapping: -|-|x|x|-
|
||||
// -|A|B|C|x
|
||||
// -|D|E|F|x
|
||||
// -|-|H|I|-
|
||||
// -|-|-|-|-
|
||||
if (blendResult.y != BLEND_NONE) {
|
||||
float dist_B_I = ColorDist(B, I);
|
||||
float dist_F_A = ColorDist(F, A);
|
||||
bool doLineBlend = (blendResult.y == BLEND_DOMINANT ||
|
||||
!((blendResult.x != BLEND_NONE && !IsPixEqual(E, I)) ||
|
||||
(blendResult.z != BLEND_NONE && !IsPixEqual(E, A)) ||
|
||||
(IsPixEqual(I, F) && IsPixEqual(F, C) && IsPixEqual(C, B) &&
|
||||
IsPixEqual(B, A) && !IsPixEqual(E, C))));
|
||||
vec2 origin = vec2(1.0 / sqrt(2.0), 0.0);
|
||||
vec2 direction = vec2(-1.0, -1.0);
|
||||
if (doLineBlend) {
|
||||
bool haveShallowLine =
|
||||
(STEEP_DIRECTION_THRESHOLD * dist_B_I <= dist_F_A) && E != I && H != I;
|
||||
bool haveSteepLine =
|
||||
(STEEP_DIRECTION_THRESHOLD * dist_F_A <= dist_B_I) && E != A && D != A;
|
||||
origin = haveShallowLine ? vec2(0.25, 0.0) : vec2(0.5, 0.0);
|
||||
direction.y -= haveShallowLine ? 1.0 : 0.0;
|
||||
direction.x -= haveSteepLine ? 1.0 : 0.0;
|
||||
}
|
||||
vec4 blendPix = mix(F, B, step(ColorDist(E, B), ColorDist(E, F)));
|
||||
res = mix(res, blendPix, GetLeftRatio(pos, origin, direction));
|
||||
}
|
||||
// Pixel Tap Mapping: -|x|x|-|-
|
||||
// x|A|B|C|-
|
||||
// x|D|E|F|-
|
||||
// -|G|H|-|-
|
||||
// -|-|-|-|-
|
||||
if (blendResult.x != BLEND_NONE) {
|
||||
float dist_D_C = ColorDist(D, C);
|
||||
float dist_B_G = ColorDist(B, G);
|
||||
bool doLineBlend = (blendResult.x == BLEND_DOMINANT ||
|
||||
!((blendResult.w != BLEND_NONE && !IsPixEqual(E, C)) ||
|
||||
(blendResult.y != BLEND_NONE && !IsPixEqual(E, G)) ||
|
||||
(IsPixEqual(C, B) && IsPixEqual(B, A) && IsPixEqual(A, D) &&
|
||||
IsPixEqual(D, G) && !IsPixEqual(E, A))));
|
||||
vec2 origin = vec2(0.0, -1.0 / sqrt(2.0));
|
||||
vec2 direction = vec2(-1.0, 1.0);
|
||||
if (doLineBlend) {
|
||||
bool haveShallowLine =
|
||||
(STEEP_DIRECTION_THRESHOLD * dist_D_C <= dist_B_G) && E != C && F != C;
|
||||
bool haveSteepLine =
|
||||
(STEEP_DIRECTION_THRESHOLD * dist_B_G <= dist_D_C) && E != G && H != G;
|
||||
origin = haveShallowLine ? vec2(0.0, -0.25) : vec2(0.0, -0.5);
|
||||
direction.x -= haveShallowLine ? 1.0 : 0.0;
|
||||
direction.y += haveSteepLine ? 1.0 : 0.0;
|
||||
}
|
||||
vec4 blendPix = mix(D, B, step(ColorDist(E, B), ColorDist(E, D)));
|
||||
res = mix(res, blendPix, GetLeftRatio(pos, origin, direction));
|
||||
}
|
||||
frag_color = res;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2019 bloc97
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//? #version 430 core
|
||||
precision mediump float;
|
||||
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 0) out float frag_color;
|
||||
|
||||
layout(binding = 2) uniform sampler2D tex_input;
|
||||
|
||||
void main() {
|
||||
vec2 t = textureLodOffset(tex_input, tex_coord, 0.0, ivec2(0, 1)).xy;
|
||||
vec2 c = textureLod(tex_input, tex_coord, 0.0).xy;
|
||||
vec2 b = textureLodOffset(tex_input, tex_coord, 0.0, ivec2(0, -1)).xy;
|
||||
|
||||
vec2 grad = vec2(t.x + 2.0 * c.x + b.x, b.y - t.y);
|
||||
|
||||
frag_color = 1.0 - length(grad);
|
||||
}
|
||||
Reference in New Issue
Block a user