2020-05-17 20:45:58 +02:00
|
|
|
// Copyright 2014 Hajime Hoshi
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package opengl
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
2022-02-06 12:41:32 +01:00
|
|
|
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
2020-05-17 20:45:58 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// glslReservedKeywords is a set of reserved keywords that cannot be used as an indentifier on some environments.
|
|
|
|
// See https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf.
|
|
|
|
var glslReservedKeywords = map[string]struct{}{
|
|
|
|
"common": {}, "partition": {}, "active": {},
|
|
|
|
"asm": {},
|
|
|
|
"class": {}, "union": {}, "enum": {}, "typedef": {}, "template": {}, "this": {},
|
|
|
|
"resource": {},
|
|
|
|
"goto": {},
|
|
|
|
"inline": {}, "noinline": {}, "public": {}, "static": {}, "extern": {}, "external": {}, "interface": {},
|
|
|
|
"long": {}, "short": {}, "half": {}, "fixed": {}, "unsigned": {}, "superp": {},
|
|
|
|
"input": {}, "output": {},
|
|
|
|
"hvec2": {}, "hvec3": {}, "hvec4": {}, "fvec2": {}, "fvec3": {}, "fvec4": {},
|
|
|
|
"filter": {},
|
|
|
|
"sizeof": {}, "cast": {},
|
|
|
|
"namespace": {}, "using": {},
|
|
|
|
"sampler3DRect": {},
|
|
|
|
}
|
|
|
|
|
|
|
|
var glslIdentifier = regexp.MustCompile(`[_a-zA-Z][_a-zA-Z0-9]*`)
|
|
|
|
|
|
|
|
func checkGLSL(src string) {
|
|
|
|
for _, l := range strings.Split(src, "\n") {
|
|
|
|
if strings.Contains(l, "//") {
|
|
|
|
l = l[:strings.Index(l, "//")]
|
|
|
|
}
|
|
|
|
for _, token := range glslIdentifier.FindAllString(l, -1) {
|
|
|
|
if _, ok := glslReservedKeywords[token]; ok {
|
|
|
|
panic(fmt.Sprintf("opengl: %q is a reserved keyword", token))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func vertexShaderStr() string {
|
|
|
|
src := shaderStrVertex
|
|
|
|
checkGLSL(src)
|
|
|
|
return src
|
|
|
|
}
|
|
|
|
|
2022-02-06 12:41:32 +01:00
|
|
|
func fragmentShaderStr(useColorM bool, filter graphicsdriver.Filter, address graphicsdriver.Address) string {
|
2020-05-17 20:45:58 +02:00
|
|
|
replaces := map[string]string{
|
2022-02-06 12:41:32 +01:00
|
|
|
"{{.AddressClampToZero}}": fmt.Sprintf("%d", graphicsdriver.AddressClampToZero),
|
|
|
|
"{{.AddressRepeat}}": fmt.Sprintf("%d", graphicsdriver.AddressRepeat),
|
|
|
|
"{{.AddressUnsafe}}": fmt.Sprintf("%d", graphicsdriver.AddressUnsafe),
|
2020-05-17 20:45:58 +02:00
|
|
|
}
|
|
|
|
src := shaderStrFragment
|
|
|
|
for k, v := range replaces {
|
|
|
|
src = strings.Replace(src, k, v, -1)
|
|
|
|
}
|
|
|
|
|
|
|
|
var defs []string
|
|
|
|
|
|
|
|
if useColorM {
|
|
|
|
defs = append(defs, "#define USE_COLOR_MATRIX")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch filter {
|
2022-02-06 12:41:32 +01:00
|
|
|
case graphicsdriver.FilterNearest:
|
2020-05-17 20:45:58 +02:00
|
|
|
defs = append(defs, "#define FILTER_NEAREST")
|
2022-02-06 12:41:32 +01:00
|
|
|
case graphicsdriver.FilterLinear:
|
2020-05-17 20:45:58 +02:00
|
|
|
defs = append(defs, "#define FILTER_LINEAR")
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("opengl: invalid filter: %d", filter))
|
|
|
|
}
|
|
|
|
|
|
|
|
switch address {
|
2022-02-06 12:41:32 +01:00
|
|
|
case graphicsdriver.AddressClampToZero:
|
2020-05-17 20:45:58 +02:00
|
|
|
defs = append(defs, "#define ADDRESS_CLAMP_TO_ZERO")
|
2022-02-06 12:41:32 +01:00
|
|
|
case graphicsdriver.AddressRepeat:
|
2020-05-17 20:45:58 +02:00
|
|
|
defs = append(defs, "#define ADDRESS_REPEAT")
|
2022-02-06 12:41:32 +01:00
|
|
|
case graphicsdriver.AddressUnsafe:
|
2020-06-24 17:47:18 +02:00
|
|
|
defs = append(defs, "#define ADDRESS_UNSAFE")
|
2020-05-17 20:45:58 +02:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("opengl: invalid address: %d", address))
|
|
|
|
}
|
|
|
|
|
|
|
|
src = strings.Replace(src, "{{.Definitions}}", strings.Join(defs, "\n"), -1)
|
|
|
|
|
|
|
|
checkGLSL(src)
|
|
|
|
return src
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
shaderStrVertex = `
|
|
|
|
uniform vec2 viewport_size;
|
2020-07-19 21:06:35 +02:00
|
|
|
attribute vec2 A0;
|
|
|
|
attribute vec2 A1;
|
|
|
|
attribute vec4 A2;
|
2020-05-17 20:45:58 +02:00
|
|
|
varying vec2 varying_tex;
|
|
|
|
varying vec4 varying_color_scale;
|
|
|
|
|
|
|
|
void main(void) {
|
2020-07-19 21:06:35 +02:00
|
|
|
varying_tex = A1;
|
internal/atlas: optimization: send premultiplied alpha from vertex to fragment shader. (#1996)
Note that this applies only to the builtin shaders - interface for Kage stays
unchanged for compatibility.
Minor compatibility delta: when interpolating alpha values, previous code has
created nonsense values, such as, when interpolating from
fully-transparent-black (0,0,0,0) to opaque-white (1,1,1,1), something like
half-transparent-grey (0.25,0.25,0.25,0.5) where half-transparent-white
(0.5,0.5,0.5,0.5) is used by the new code.
I assume this is a strict improvement, however this may warrant some testing.
Possible later improvement could be moving the premultiplication from fragment
shader to CPU. Did not do this as it makes the code rather inconsistent of Kage
vs built-in shader usage.
Updates #1772
2022-02-23 18:27:50 +01:00
|
|
|
|
|
|
|
// Fragment shader wants premultiplied alpha.
|
|
|
|
varying_color_scale = vec4(A2.rgb, 1) * A2.a;
|
2020-05-17 20:45:58 +02:00
|
|
|
|
|
|
|
mat4 projection_matrix = mat4(
|
|
|
|
vec4(2.0 / viewport_size.x, 0, 0, 0),
|
|
|
|
vec4(0, 2.0 / viewport_size.y, 0, 0),
|
|
|
|
vec4(0, 0, 1, 0),
|
|
|
|
vec4(-1, -1, 0, 1)
|
|
|
|
);
|
2020-07-19 21:06:35 +02:00
|
|
|
gl_Position = projection_matrix * vec4(A0, 0, 1);
|
2020-05-17 20:45:58 +02:00
|
|
|
}
|
|
|
|
`
|
|
|
|
shaderStrFragment = `
|
|
|
|
#if defined(GL_ES)
|
|
|
|
precision mediump float;
|
|
|
|
#else
|
|
|
|
#define lowp
|
|
|
|
#define mediump
|
|
|
|
#define highp
|
|
|
|
#endif
|
|
|
|
|
|
|
|
{{.Definitions}}
|
|
|
|
|
2020-07-05 20:36:15 +02:00
|
|
|
uniform sampler2D T0;
|
2020-06-29 17:02:33 +02:00
|
|
|
uniform vec4 source_region;
|
2020-05-17 20:45:58 +02:00
|
|
|
|
|
|
|
#if defined(USE_COLOR_MATRIX)
|
|
|
|
uniform mat4 color_matrix_body;
|
|
|
|
uniform vec4 color_matrix_translation;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
uniform highp vec2 source_size;
|
|
|
|
|
|
|
|
varying highp vec2 varying_tex;
|
|
|
|
varying highp vec4 varying_color_scale;
|
|
|
|
|
2020-07-02 16:06:58 +02:00
|
|
|
highp vec2 adjustTexelByAddress(highp vec2 p, highp vec4 source_region) {
|
2020-05-17 20:45:58 +02:00
|
|
|
#if defined(ADDRESS_CLAMP_TO_ZERO)
|
|
|
|
return p;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(ADDRESS_REPEAT)
|
2020-07-02 16:06:58 +02:00
|
|
|
highp vec2 o = vec2(source_region[0], source_region[1]);
|
|
|
|
highp vec2 size = vec2(source_region[2] - source_region[0], source_region[3] - source_region[1]);
|
2022-03-03 19:47:55 +01:00
|
|
|
return vec2(mod((p.x - o.x), size.x) + o.x, mod((p.y - o.y), size.y) + o.y);
|
2020-05-17 20:45:58 +02:00
|
|
|
#endif
|
2020-06-24 17:47:18 +02:00
|
|
|
|
|
|
|
#if defined(ADDRESS_UNSAFE)
|
|
|
|
return p;
|
|
|
|
#endif
|
2020-05-17 20:45:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void main(void) {
|
|
|
|
highp vec2 pos = varying_tex;
|
|
|
|
|
|
|
|
#if defined(FILTER_NEAREST)
|
|
|
|
vec4 color;
|
2020-06-24 17:47:18 +02:00
|
|
|
# if defined(ADDRESS_UNSAFE)
|
2020-07-05 20:36:15 +02:00
|
|
|
color = texture2D(T0, pos);
|
2020-06-24 17:47:18 +02:00
|
|
|
# else
|
2020-06-29 17:02:33 +02:00
|
|
|
pos = adjustTexelByAddress(pos, source_region);
|
|
|
|
if (source_region[0] <= pos.x &&
|
|
|
|
source_region[1] <= pos.y &&
|
|
|
|
pos.x < source_region[2] &&
|
|
|
|
pos.y < source_region[3]) {
|
2020-07-05 20:36:15 +02:00
|
|
|
color = texture2D(T0, pos);
|
2020-05-17 20:45:58 +02:00
|
|
|
} else {
|
|
|
|
color = vec4(0, 0, 0, 0);
|
|
|
|
}
|
2020-06-24 17:47:18 +02:00
|
|
|
# endif // defined(ADDRESS_UNSAFE)
|
2020-06-24 18:06:30 +02:00
|
|
|
#endif // defined(FILTER_NEAREST)
|
2020-05-17 20:45:58 +02:00
|
|
|
|
|
|
|
#if defined(FILTER_LINEAR)
|
|
|
|
vec4 color;
|
|
|
|
highp vec2 texel_size = 1.0 / source_size;
|
2020-06-24 16:52:39 +02:00
|
|
|
|
|
|
|
// Shift 1/512 [texel] to avoid the tie-breaking issue.
|
|
|
|
// As all the vertex positions are aligned to 1/16 [pixel], this shiting should work in most cases.
|
2020-06-24 13:46:32 +02:00
|
|
|
highp vec2 p0 = pos - (texel_size) / 2.0 + (texel_size / 512.0);
|
|
|
|
highp vec2 p1 = pos + (texel_size) / 2.0 + (texel_size / 512.0);
|
2020-05-17 20:45:58 +02:00
|
|
|
|
2020-06-24 17:47:18 +02:00
|
|
|
# if !defined(ADDRESS_UNSAFE)
|
2020-06-29 17:02:33 +02:00
|
|
|
p0 = adjustTexelByAddress(p0, source_region);
|
|
|
|
p1 = adjustTexelByAddress(p1, source_region);
|
2020-06-24 17:47:18 +02:00
|
|
|
# endif // defined(ADDRESS_UNSAFE)
|
2020-05-17 20:45:58 +02:00
|
|
|
|
2020-07-05 20:36:15 +02:00
|
|
|
vec4 c0 = texture2D(T0, p0);
|
|
|
|
vec4 c1 = texture2D(T0, vec2(p1.x, p0.y));
|
|
|
|
vec4 c2 = texture2D(T0, vec2(p0.x, p1.y));
|
|
|
|
vec4 c3 = texture2D(T0, p1);
|
2020-06-24 17:47:18 +02:00
|
|
|
# if !defined(ADDRESS_UNSAFE)
|
2020-06-29 17:02:33 +02:00
|
|
|
if (p0.x < source_region[0]) {
|
2020-05-17 20:45:58 +02:00
|
|
|
c0 = vec4(0, 0, 0, 0);
|
|
|
|
c2 = vec4(0, 0, 0, 0);
|
|
|
|
}
|
2020-06-29 17:02:33 +02:00
|
|
|
if (p0.y < source_region[1]) {
|
2020-05-17 20:45:58 +02:00
|
|
|
c0 = vec4(0, 0, 0, 0);
|
|
|
|
c1 = vec4(0, 0, 0, 0);
|
|
|
|
}
|
2020-06-29 17:02:33 +02:00
|
|
|
if (source_region[2] <= p1.x) {
|
2020-05-17 20:45:58 +02:00
|
|
|
c1 = vec4(0, 0, 0, 0);
|
|
|
|
c3 = vec4(0, 0, 0, 0);
|
|
|
|
}
|
2020-06-29 17:02:33 +02:00
|
|
|
if (source_region[3] <= p1.y) {
|
2020-05-17 20:45:58 +02:00
|
|
|
c2 = vec4(0, 0, 0, 0);
|
|
|
|
c3 = vec4(0, 0, 0, 0);
|
|
|
|
}
|
2020-06-24 17:47:18 +02:00
|
|
|
# endif // defined(ADDRESS_UNSAFE)
|
2020-05-17 20:45:58 +02:00
|
|
|
|
|
|
|
vec2 rate = fract(p0 * source_size);
|
|
|
|
color = mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y);
|
2020-06-24 18:06:30 +02:00
|
|
|
#endif // defined(FILTER_LINEAR)
|
2020-05-17 20:45:58 +02:00
|
|
|
|
2020-06-24 18:06:30 +02:00
|
|
|
# if defined(USE_COLOR_MATRIX)
|
2020-05-17 20:45:58 +02:00
|
|
|
// Un-premultiply alpha.
|
|
|
|
// When the alpha is 0, 1.0 - sign(alpha) is 1.0, which means division does nothing.
|
|
|
|
color.rgb /= color.a + (1.0 - sign(color.a));
|
|
|
|
// Apply the color matrix or scale.
|
|
|
|
color = (color_matrix_body * color) + color_matrix_translation;
|
|
|
|
// Premultiply alpha
|
|
|
|
color.rgb *= color.a;
|
internal/atlas: optimization: send premultiplied alpha from vertex to fragment shader. (#1996)
Note that this applies only to the builtin shaders - interface for Kage stays
unchanged for compatibility.
Minor compatibility delta: when interpolating alpha values, previous code has
created nonsense values, such as, when interpolating from
fully-transparent-black (0,0,0,0) to opaque-white (1,1,1,1), something like
half-transparent-grey (0.25,0.25,0.25,0.5) where half-transparent-white
(0.5,0.5,0.5,0.5) is used by the new code.
I assume this is a strict improvement, however this may warrant some testing.
Possible later improvement could be moving the premultiplication from fragment
shader to CPU. Did not do this as it makes the code rather inconsistent of Kage
vs built-in shader usage.
Updates #1772
2022-02-23 18:27:50 +01:00
|
|
|
// Apply color scale.
|
|
|
|
color *= varying_color_scale;
|
|
|
|
// Clamp the output.
|
|
|
|
color.rgb = min(color.rgb, color.a);
|
2020-06-24 18:06:30 +02:00
|
|
|
# else
|
internal/atlas: optimization: send premultiplied alpha from vertex to fragment shader. (#1996)
Note that this applies only to the builtin shaders - interface for Kage stays
unchanged for compatibility.
Minor compatibility delta: when interpolating alpha values, previous code has
created nonsense values, such as, when interpolating from
fully-transparent-black (0,0,0,0) to opaque-white (1,1,1,1), something like
half-transparent-grey (0.25,0.25,0.25,0.5) where half-transparent-white
(0.5,0.5,0.5,0.5) is used by the new code.
I assume this is a strict improvement, however this may warrant some testing.
Possible later improvement could be moving the premultiplication from fragment
shader to CPU. Did not do this as it makes the code rather inconsistent of Kage
vs built-in shader usage.
Updates #1772
2022-02-23 18:27:50 +01:00
|
|
|
// Apply color scale.
|
|
|
|
color *= varying_color_scale;
|
|
|
|
// No clamping needed as the color matrix shader is used then.
|
2020-06-24 18:06:30 +02:00
|
|
|
# endif // defined(USE_COLOR_MATRIX)
|
2020-05-17 20:45:58 +02:00
|
|
|
|
|
|
|
gl_FragColor = color;
|
|
|
|
}
|
|
|
|
`
|
|
|
|
)
|