2018-11-12 16:00:10 +01:00
|
|
|
// Copyright 2018 The Ebiten Authors
|
|
|
|
//
|
|
|
|
// 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 metal
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2021-02-26 19:40:36 +01:00
|
|
|
"math"
|
2021-07-06 10:25:11 +02:00
|
|
|
"sort"
|
2018-11-12 16:00:10 +01:00
|
|
|
"strings"
|
|
|
|
"unsafe"
|
|
|
|
|
2020-10-03 19:35:13 +02:00
|
|
|
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
2022-02-06 12:41:32 +01:00
|
|
|
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
2020-10-03 19:35:13 +02:00
|
|
|
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/ca"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
|
2018-11-12 16:00:10 +01:00
|
|
|
)
|
|
|
|
|
2019-06-08 22:44:14 +02:00
|
|
|
// #cgo CFLAGS: -x objective-c
|
2020-07-24 21:18:18 +02:00
|
|
|
// #cgo !ios CFLAGS: -mmacosx-version-min=10.12
|
2019-04-20 08:17:59 +02:00
|
|
|
// #cgo LDFLAGS: -framework Foundation
|
|
|
|
//
|
|
|
|
// #import <Foundation/Foundation.h>
|
|
|
|
//
|
|
|
|
// static void* allocAutoreleasePool() {
|
|
|
|
// return [[NSAutoreleasePool alloc] init];
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// static void releaseAutoreleasePool(void* pool) {
|
|
|
|
// [(NSAutoreleasePool*)pool release];
|
|
|
|
// }
|
|
|
|
import "C"
|
|
|
|
|
2018-11-12 16:00:10 +01:00
|
|
|
const source = `#include <metal_stdlib>
|
|
|
|
|
2019-02-13 13:46:39 +01:00
|
|
|
#define FILTER_NEAREST {{.FilterNearest}}
|
|
|
|
#define FILTER_LINEAR {{.FilterLinear}}
|
|
|
|
#define FILTER_SCREEN {{.FilterScreen}}
|
2018-11-12 16:00:10 +01:00
|
|
|
|
2019-02-13 13:46:39 +01:00
|
|
|
#define ADDRESS_CLAMP_TO_ZERO {{.AddressClampToZero}}
|
|
|
|
#define ADDRESS_REPEAT {{.AddressRepeat}}
|
2020-06-24 17:47:18 +02:00
|
|
|
#define ADDRESS_UNSAFE {{.AddressUnsafe}}
|
2018-12-23 19:00:00 +01:00
|
|
|
|
2018-11-12 16:00:10 +01:00
|
|
|
using namespace metal;
|
|
|
|
|
|
|
|
struct VertexIn {
|
|
|
|
packed_float2 position;
|
2018-12-22 17:40:43 +01:00
|
|
|
packed_float2 tex;
|
2018-11-12 16:00:10 +01:00
|
|
|
packed_float4 color;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct VertexOut {
|
|
|
|
float4 position [[position]];
|
|
|
|
float2 tex;
|
|
|
|
float4 color;
|
|
|
|
};
|
|
|
|
|
|
|
|
vertex VertexOut VertexShader(
|
|
|
|
uint vid [[vertex_id]],
|
2020-06-24 17:47:18 +02:00
|
|
|
const device VertexIn* vertices [[buffer(0)]],
|
2018-11-12 16:00:10 +01:00
|
|
|
constant float2& viewport_size [[buffer(1)]]
|
|
|
|
) {
|
2022-02-26 17:49:56 +01:00
|
|
|
// In Metal, the NDC's Y direction (upward) and the framebuffer's Y direction (downward) don't
|
|
|
|
// match. Then, the Y direction must be inverted.
|
2018-11-12 16:00:10 +01:00
|
|
|
float4x4 projectionMatrix = float4x4(
|
|
|
|
float4(2.0 / viewport_size.x, 0, 0, 0),
|
2022-02-26 17:49:56 +01:00
|
|
|
float4(0, -2.0 / viewport_size.y, 0, 0),
|
2018-11-12 16:00:10 +01:00
|
|
|
float4(0, 0, 1, 0),
|
2022-02-26 17:49:56 +01:00
|
|
|
float4(-1, 1, 0, 1)
|
2018-11-12 16:00:10 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
VertexIn in = vertices[vid];
|
|
|
|
VertexOut out = {
|
2020-05-08 11:12:30 +02:00
|
|
|
.position = projectionMatrix * float4(in.position, 0, 1),
|
2018-12-22 17:40:43 +01:00
|
|
|
.tex = in.tex,
|
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.
|
|
|
|
.color = float4(in.color.rgb, 1) * in.color.a,
|
2018-11-12 16:00:10 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2022-03-03 19:47:55 +01:00
|
|
|
float EuclideanMod(float x, float y) {
|
|
|
|
// Assume that y is always positive.
|
2018-12-23 19:00:00 +01:00
|
|
|
return x - y * floor(x/y);
|
|
|
|
}
|
|
|
|
|
2019-02-13 13:46:39 +01:00
|
|
|
template<uint8_t address>
|
2020-06-29 17:02:33 +02:00
|
|
|
float2 AdjustTexelByAddress(float2 p, float4 source_region);
|
2019-02-14 01:57:39 +01:00
|
|
|
|
|
|
|
template<>
|
2020-06-29 17:02:33 +02:00
|
|
|
inline float2 AdjustTexelByAddress<ADDRESS_CLAMP_TO_ZERO>(float2 p, float4 source_region) {
|
2019-02-14 01:57:39 +01:00
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<>
|
2020-06-29 17:02:33 +02:00
|
|
|
inline float2 AdjustTexelByAddress<ADDRESS_REPEAT>(float2 p, float4 source_region) {
|
|
|
|
float2 o = float2(source_region[0], source_region[1]);
|
|
|
|
float2 size = float2(source_region[2] - source_region[0], source_region[3] - source_region[1]);
|
2022-03-03 19:47:55 +01:00
|
|
|
return float2(EuclideanMod((p.x - o.x), size.x) + o.x, EuclideanMod((p.y - o.y), size.y) + o.y);
|
2018-12-23 19:00:00 +01:00
|
|
|
}
|
|
|
|
|
2019-02-13 13:46:39 +01:00
|
|
|
template<uint8_t filter, uint8_t address>
|
2019-02-17 11:01:43 +01:00
|
|
|
struct ColorFromTexel;
|
2018-11-12 16:00:10 +01:00
|
|
|
|
2020-08-08 11:05:58 +02:00
|
|
|
constexpr sampler texture_sampler{filter::nearest};
|
|
|
|
|
2020-06-24 17:47:18 +02:00
|
|
|
template<>
|
|
|
|
struct ColorFromTexel<FILTER_NEAREST, ADDRESS_UNSAFE> {
|
2022-03-05 14:12:05 +01:00
|
|
|
inline float4 Do(VertexOut v, texture2d<float> texture, constant float2& source_size, constant float4& source_region, float scale) {
|
2020-06-24 17:47:18 +02:00
|
|
|
float2 p = v.tex;
|
|
|
|
return texture.sample(texture_sampler, p);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-02-15 01:42:25 +01:00
|
|
|
template<uint8_t address>
|
2019-02-17 11:01:43 +01:00
|
|
|
struct ColorFromTexel<FILTER_NEAREST, address> {
|
2022-03-05 14:12:05 +01:00
|
|
|
inline float4 Do(VertexOut v, texture2d<float> texture, constant float2& source_size, constant float4& source_region, float scale) {
|
2020-06-29 17:02:33 +02:00
|
|
|
float2 p = AdjustTexelByAddress<address>(v.tex, source_region);
|
|
|
|
if (source_region[0] <= p.x &&
|
|
|
|
source_region[1] <= p.y &&
|
|
|
|
p.x < source_region[2] &&
|
|
|
|
p.y < source_region[3]) {
|
2019-02-17 09:31:05 +01:00
|
|
|
return texture.sample(texture_sampler, p);
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
2019-02-17 09:31:05 +01:00
|
|
|
return 0.0;
|
2019-02-15 01:42:25 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-06-24 17:47:18 +02:00
|
|
|
template<>
|
|
|
|
struct ColorFromTexel<FILTER_LINEAR, ADDRESS_UNSAFE> {
|
2022-03-05 14:12:05 +01:00
|
|
|
inline float4 Do(VertexOut v, texture2d<float> texture, constant float2& source_size, constant float4& source_region, float scale) {
|
2020-06-24 17:47:18 +02:00
|
|
|
const float2 texel_size = 1 / source_size;
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
float2 p0 = v.tex - texel_size / 2.0 + (texel_size / 512.0);
|
|
|
|
float2 p1 = v.tex + texel_size / 2.0 + (texel_size / 512.0);
|
|
|
|
|
|
|
|
float4 c0 = texture.sample(texture_sampler, p0);
|
|
|
|
float4 c1 = texture.sample(texture_sampler, float2(p1.x, p0.y));
|
|
|
|
float4 c2 = texture.sample(texture_sampler, float2(p0.x, p1.y));
|
|
|
|
float4 c3 = texture.sample(texture_sampler, p1);
|
|
|
|
|
|
|
|
float2 rate = fract(p0 * source_size);
|
|
|
|
return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-02-15 01:42:25 +01:00
|
|
|
template<uint8_t address>
|
2019-02-17 11:01:43 +01:00
|
|
|
struct ColorFromTexel<FILTER_LINEAR, address> {
|
2022-03-05 14:12:05 +01:00
|
|
|
inline float4 Do(VertexOut v, texture2d<float> texture, constant float2& source_size, constant float4& source_region, float scale) {
|
2019-02-15 01:42:25 +01:00
|
|
|
const float2 texel_size = 1 / source_size;
|
|
|
|
|
2020-06-24 13:46:32 +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.
|
|
|
|
float2 p0 = v.tex - texel_size / 2.0 + (texel_size / 512.0);
|
|
|
|
float2 p1 = v.tex + texel_size / 2.0 + (texel_size / 512.0);
|
2020-06-29 17:02:33 +02:00
|
|
|
p0 = AdjustTexelByAddress<address>(p0, source_region);
|
|
|
|
p1 = AdjustTexelByAddress<address>(p1, source_region);
|
2018-11-12 16:00:10 +01:00
|
|
|
|
|
|
|
float4 c0 = texture.sample(texture_sampler, p0);
|
|
|
|
float4 c1 = texture.sample(texture_sampler, float2(p1.x, p0.y));
|
|
|
|
float4 c2 = texture.sample(texture_sampler, float2(p0.x, p1.y));
|
|
|
|
float4 c3 = texture.sample(texture_sampler, p1);
|
|
|
|
|
2020-06-29 17:02:33 +02:00
|
|
|
if (p0.x < source_region[0]) {
|
2018-11-12 16:00:10 +01:00
|
|
|
c0 = 0;
|
|
|
|
c2 = 0;
|
|
|
|
}
|
2020-06-29 17:02:33 +02:00
|
|
|
if (p0.y < source_region[1]) {
|
2018-11-12 16:00:10 +01:00
|
|
|
c0 = 0;
|
|
|
|
c1 = 0;
|
|
|
|
}
|
2020-06-29 17:02:33 +02:00
|
|
|
if (source_region[2] <= p1.x) {
|
2018-11-12 16:00:10 +01:00
|
|
|
c1 = 0;
|
|
|
|
c3 = 0;
|
|
|
|
}
|
2020-06-29 17:02:33 +02:00
|
|
|
if (source_region[3] <= p1.y) {
|
2018-11-12 16:00:10 +01:00
|
|
|
c2 = 0;
|
|
|
|
c3 = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
float2 rate = fract(p0 * source_size);
|
2019-02-15 01:42:25 +01:00
|
|
|
return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template<uint8_t address>
|
2019-02-17 11:01:43 +01:00
|
|
|
struct ColorFromTexel<FILTER_SCREEN, address> {
|
2022-03-05 14:12:05 +01:00
|
|
|
inline float4 Do(VertexOut v, texture2d<float> texture, constant float2& source_size, constant float4& source_region, float scale) {
|
2019-02-15 01:42:25 +01:00
|
|
|
const float2 texel_size = 1 / source_size;
|
|
|
|
|
2020-06-24 13:46:32 +02:00
|
|
|
float2 p0 = v.tex - texel_size / 2.0 / scale + (texel_size / 512.0);
|
|
|
|
float2 p1 = v.tex + texel_size / 2.0 / scale + (texel_size / 512.0);
|
2018-11-12 16:00:10 +01:00
|
|
|
|
|
|
|
float4 c0 = texture.sample(texture_sampler, p0);
|
|
|
|
float4 c1 = texture.sample(texture_sampler, float2(p1.x, p0.y));
|
|
|
|
float4 c2 = texture.sample(texture_sampler, float2(p0.x, p1.y));
|
|
|
|
float4 c3 = texture.sample(texture_sampler, p1);
|
|
|
|
|
|
|
|
float2 rate_center = float2(1.0, 1.0) - texel_size / 2.0 / scale;
|
|
|
|
float2 rate = clamp(((fract(p0 * source_size) - rate_center) * scale) + rate_center, 0.0, 1.0);
|
2019-02-15 01:42:25 +01:00
|
|
|
return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y);
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
2019-02-15 01:42:25 +01:00
|
|
|
};
|
2018-11-12 16:00:10 +01:00
|
|
|
|
2019-02-16 09:08:53 +01:00
|
|
|
template<bool useColorM, uint8_t filter, uint8_t address>
|
2019-02-17 11:01:43 +01:00
|
|
|
struct FragmentShaderImpl {
|
|
|
|
inline float4 Do(
|
|
|
|
VertexOut v,
|
|
|
|
texture2d<float> texture,
|
|
|
|
constant float2& source_size,
|
|
|
|
constant float4x4& color_matrix_body,
|
|
|
|
constant float4& color_matrix_translation,
|
2022-03-05 14:12:05 +01:00
|
|
|
constant float4& source_region,
|
|
|
|
constant float& scale) {
|
|
|
|
float4 c = ColorFromTexel<filter, address>().Do(v, texture, source_size, source_region, scale);
|
2019-02-17 11:01:43 +01:00
|
|
|
if (useColorM) {
|
|
|
|
c.rgb /= c.a + (1.0 - sign(c.a));
|
|
|
|
c = (color_matrix_body * c) + color_matrix_translation;
|
|
|
|
c.rgb *= c.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
|
|
|
c *= v.color;
|
|
|
|
c.rgb = min(c.rgb, c.a);
|
2019-02-17 11:01:43 +01: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
|
|
|
c *= v.color;
|
2019-02-17 11:01:43 +01:00
|
|
|
}
|
|
|
|
return c;
|
2019-02-16 09:08:53 +01:00
|
|
|
}
|
2019-02-17 11:01:43 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
template<bool useColorM, uint8_t address>
|
|
|
|
struct FragmentShaderImpl<useColorM, FILTER_SCREEN, address> {
|
|
|
|
inline float4 Do(
|
|
|
|
VertexOut v,
|
|
|
|
texture2d<float> texture,
|
|
|
|
constant float2& source_size,
|
|
|
|
constant float4x4& color_matrix_body,
|
|
|
|
constant float4& color_matrix_translation,
|
2022-03-05 14:12:05 +01:00
|
|
|
constant float4& source_region,
|
|
|
|
constant float& scale) {
|
|
|
|
return ColorFromTexel<FILTER_SCREEN, address>().Do(v, texture, source_size, source_region, scale);
|
2019-02-17 11:01:43 +01:00
|
|
|
}
|
|
|
|
};
|
2019-02-13 13:46:39 +01:00
|
|
|
|
2019-02-13 15:36:45 +01:00
|
|
|
// Define Foo and FooCp macros to force macro replacement.
|
2019-02-13 13:46:39 +01:00
|
|
|
// See "6.10.3.1 Argument substitution" in ISO/IEC 9899.
|
|
|
|
|
2019-02-16 09:08:53 +01:00
|
|
|
#define FragmentShaderFunc(useColorM, filter, address) \
|
|
|
|
FragmentShaderFuncCp(useColorM, filter, address)
|
2019-02-13 13:46:39 +01:00
|
|
|
|
2019-02-16 09:08:53 +01:00
|
|
|
#define FragmentShaderFuncCp(useColorM, filter, address) \
|
|
|
|
fragment float4 FragmentShader_##useColorM##_##filter##_##address( \
|
2019-02-13 13:46:39 +01:00
|
|
|
VertexOut v [[stage_in]], \
|
|
|
|
texture2d<float> texture [[texture(0)]], \
|
2019-02-14 02:54:32 +01:00
|
|
|
constant float2& source_size [[buffer(2)]], \
|
|
|
|
constant float4x4& color_matrix_body [[buffer(3)]], \
|
|
|
|
constant float4& color_matrix_translation [[buffer(4)]], \
|
2022-03-05 14:12:05 +01:00
|
|
|
constant float4& source_region [[buffer(5)]], \
|
|
|
|
constant float& scale [[buffer(6)]]) { \
|
2019-02-17 11:01:43 +01:00
|
|
|
return FragmentShaderImpl<useColorM, filter, address>().Do( \
|
2022-03-05 14:12:05 +01:00
|
|
|
v, texture, source_size, color_matrix_body, color_matrix_translation, source_region, scale); \
|
2019-02-13 13:46:39 +01:00
|
|
|
}
|
|
|
|
|
2019-02-16 09:08:53 +01:00
|
|
|
FragmentShaderFunc(0, FILTER_NEAREST, ADDRESS_CLAMP_TO_ZERO)
|
|
|
|
FragmentShaderFunc(0, FILTER_LINEAR, ADDRESS_CLAMP_TO_ZERO)
|
|
|
|
FragmentShaderFunc(0, FILTER_NEAREST, ADDRESS_REPEAT)
|
|
|
|
FragmentShaderFunc(0, FILTER_LINEAR, ADDRESS_REPEAT)
|
2020-06-24 17:47:18 +02:00
|
|
|
FragmentShaderFunc(0, FILTER_NEAREST, ADDRESS_UNSAFE)
|
|
|
|
FragmentShaderFunc(0, FILTER_LINEAR, ADDRESS_UNSAFE)
|
2019-02-16 09:08:53 +01:00
|
|
|
FragmentShaderFunc(1, FILTER_NEAREST, ADDRESS_CLAMP_TO_ZERO)
|
|
|
|
FragmentShaderFunc(1, FILTER_LINEAR, ADDRESS_CLAMP_TO_ZERO)
|
|
|
|
FragmentShaderFunc(1, FILTER_NEAREST, ADDRESS_REPEAT)
|
|
|
|
FragmentShaderFunc(1, FILTER_LINEAR, ADDRESS_REPEAT)
|
2020-06-24 17:47:18 +02:00
|
|
|
FragmentShaderFunc(1, FILTER_NEAREST, ADDRESS_UNSAFE)
|
|
|
|
FragmentShaderFunc(1, FILTER_LINEAR, ADDRESS_UNSAFE)
|
2019-02-16 09:08:53 +01:00
|
|
|
|
2020-06-24 17:47:18 +02:00
|
|
|
FragmentShaderFunc(0, FILTER_SCREEN, ADDRESS_UNSAFE)
|
2019-02-13 13:46:39 +01:00
|
|
|
|
|
|
|
#undef FragmentShaderFuncName
|
2018-11-12 16:00:10 +01:00
|
|
|
`
|
|
|
|
|
2019-02-13 13:46:39 +01:00
|
|
|
type rpsKey struct {
|
2021-07-06 07:05:01 +02:00
|
|
|
useColorM bool
|
2022-02-06 12:41:32 +01:00
|
|
|
filter graphicsdriver.Filter
|
|
|
|
address graphicsdriver.Address
|
|
|
|
compositeMode graphicsdriver.CompositeMode
|
2021-07-06 07:05:01 +02:00
|
|
|
stencilMode stencilMode
|
|
|
|
screen bool
|
2019-02-13 13:46:39 +01:00
|
|
|
}
|
|
|
|
|
2020-04-04 10:12:24 +02:00
|
|
|
type Graphics struct {
|
2019-06-18 17:34:05 +02:00
|
|
|
view view
|
2018-11-12 16:00:10 +01:00
|
|
|
|
|
|
|
screenRPS mtl.RenderPipelineState
|
2019-02-13 13:46:39 +01:00
|
|
|
rpss map[rpsKey]mtl.RenderPipelineState
|
2018-11-12 16:00:10 +01:00
|
|
|
cq mtl.CommandQueue
|
|
|
|
cb mtl.CommandBuffer
|
2021-07-04 11:13:18 +02:00
|
|
|
rce mtl.RenderCommandEncoder
|
2021-07-06 08:05:44 +02:00
|
|
|
dsss map[stencilMode]mtl.DepthStencilState
|
2018-11-12 16:00:10 +01:00
|
|
|
|
|
|
|
screenDrawable ca.MetalDrawable
|
2021-07-02 12:26:09 +02:00
|
|
|
|
2021-07-07 17:27:33 +02:00
|
|
|
buffers map[mtl.CommandBuffer][]mtl.Buffer
|
|
|
|
unusedBuffers map[mtl.Buffer]struct{}
|
2021-07-06 10:25:11 +02:00
|
|
|
|
2021-07-07 18:56:12 +02:00
|
|
|
lastDst *Image
|
2021-07-06 07:05:01 +02:00
|
|
|
lastStencilMode stencilMode
|
2018-11-12 16:00:10 +01:00
|
|
|
|
2020-05-19 16:48:43 +02:00
|
|
|
vb mtl.Buffer
|
|
|
|
ib mtl.Buffer
|
|
|
|
|
2022-02-06 12:41:32 +01:00
|
|
|
images map[graphicsdriver.ImageID]*Image
|
|
|
|
nextImageID graphicsdriver.ImageID
|
2020-05-19 16:48:43 +02:00
|
|
|
|
2022-02-06 12:41:32 +01:00
|
|
|
shaders map[graphicsdriver.ShaderID]*Shader
|
|
|
|
nextShaderID graphicsdriver.ShaderID
|
2020-08-03 17:56:45 +02:00
|
|
|
|
2018-11-12 16:00:10 +01:00
|
|
|
src *Image
|
|
|
|
dst *Image
|
|
|
|
|
2019-11-30 16:07:41 +01:00
|
|
|
transparent bool
|
2018-11-12 16:00:10 +01:00
|
|
|
maxImageSize int
|
2020-11-10 14:48:01 +01:00
|
|
|
tmpTextures []mtl.Texture
|
2019-04-20 08:17:59 +02:00
|
|
|
|
|
|
|
pool unsafe.Pointer
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
2021-07-02 12:26:09 +02:00
|
|
|
type stencilMode int
|
|
|
|
|
|
|
|
const (
|
|
|
|
prepareStencil stencilMode = iota
|
|
|
|
drawWithStencil
|
|
|
|
noStencil
|
|
|
|
)
|
|
|
|
|
2022-02-11 15:11:49 +01:00
|
|
|
// isMetalAvailable reports whether Metal is available or not.
|
2022-03-22 17:22:39 +01:00
|
|
|
var isMetalAvailable bool
|
|
|
|
|
|
|
|
func init() {
|
2022-03-22 18:17:56 +01:00
|
|
|
if !supportsMetal() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-22 17:22:39 +01:00
|
|
|
// Initialize isMetalAvailable on the main thread.
|
2022-03-22 18:17:56 +01:00
|
|
|
// On old mac devices like iMac 2011, Metal is not supported (#779).
|
2022-03-22 17:22:39 +01:00
|
|
|
// TODO: Is there a better way to check whether Metal is available or not?
|
|
|
|
// It seems OK to call MTLCreateSystemDefaultDevice multiple times, so this should be fine.
|
2022-03-22 18:17:56 +01:00
|
|
|
if _, ok := mtl.CreateSystemDefaultDevice(); !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
isMetalAvailable = true
|
2022-03-22 17:22:39 +01:00
|
|
|
}
|
2022-02-11 15:11:49 +01:00
|
|
|
|
2020-04-04 10:12:24 +02:00
|
|
|
var theGraphics Graphics
|
2018-11-12 16:00:10 +01:00
|
|
|
|
2022-03-24 05:20:36 +01:00
|
|
|
func Get() *Graphics {
|
2022-02-11 15:11:49 +01:00
|
|
|
if !isMetalAvailable {
|
|
|
|
return nil
|
|
|
|
}
|
2020-04-04 10:12:24 +02:00
|
|
|
return &theGraphics
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
2022-03-21 14:23:07 +01:00
|
|
|
func (g *Graphics) Begin() error {
|
2020-10-12 17:39:45 +02:00
|
|
|
// NSAutoreleasePool is required to release drawable correctly (#847).
|
|
|
|
// https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/Drawables.html
|
|
|
|
g.pool = C.allocAutoreleasePool()
|
2022-03-21 14:23:07 +01:00
|
|
|
return nil
|
2019-04-20 08:17:59 +02:00
|
|
|
}
|
|
|
|
|
2022-03-21 14:23:07 +01:00
|
|
|
func (g *Graphics) End(present bool) error {
|
2022-02-21 17:37:13 +01:00
|
|
|
g.flushIfNeeded(present)
|
2020-10-12 17:39:45 +02:00
|
|
|
g.screenDrawable = ca.MetalDrawable{}
|
|
|
|
C.releaseAutoreleasePool(g.pool)
|
|
|
|
g.pool = nil
|
2022-03-21 14:23:07 +01:00
|
|
|
return nil
|
2019-04-20 08:17:59 +02:00
|
|
|
}
|
|
|
|
|
2020-09-03 17:43:51 +02:00
|
|
|
func (g *Graphics) SetWindow(window uintptr) {
|
2020-10-12 17:39:45 +02:00
|
|
|
// Note that [NSApp mainWindow] returns nil when the window is borderless.
|
|
|
|
// Then the window is needed to be given explicitly.
|
|
|
|
g.view.setWindow(window)
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
2020-04-04 10:12:24 +02:00
|
|
|
func (g *Graphics) SetUIView(uiview uintptr) {
|
2019-09-02 21:08:58 +02:00
|
|
|
// TODO: Should this be called on the main thread?
|
2020-04-04 10:12:24 +02:00
|
|
|
g.view.setUIView(uiview)
|
2019-09-02 21:08:58 +02:00
|
|
|
}
|
|
|
|
|
2021-07-06 10:25:11 +02:00
|
|
|
func pow2(x uintptr) uintptr {
|
|
|
|
var p2 uintptr = 1
|
|
|
|
for p2 < x {
|
|
|
|
p2 *= 2
|
|
|
|
}
|
|
|
|
return p2
|
|
|
|
}
|
|
|
|
|
2021-07-07 19:09:04 +02:00
|
|
|
func (g *Graphics) gcBuffers() {
|
2021-07-07 17:27:33 +02:00
|
|
|
for cb, bs := range g.buffers {
|
|
|
|
// If the command buffer still lives, the buffer must not be updated.
|
|
|
|
// TODO: Handle an error?
|
|
|
|
if cb.Status() != mtl.CommandBufferStatusCompleted {
|
2021-07-06 10:25:11 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-07-07 17:27:33 +02:00
|
|
|
for _, b := range bs {
|
|
|
|
if g.unusedBuffers == nil {
|
|
|
|
g.unusedBuffers = map[mtl.Buffer]struct{}{}
|
|
|
|
}
|
|
|
|
g.unusedBuffers[b] = struct{}{}
|
|
|
|
}
|
|
|
|
delete(g.buffers, cb)
|
|
|
|
cb.Release()
|
|
|
|
}
|
|
|
|
|
2021-07-06 14:56:25 +02:00
|
|
|
const maxUnusedBuffers = 10
|
2021-07-07 17:27:33 +02:00
|
|
|
if len(g.unusedBuffers) > maxUnusedBuffers {
|
|
|
|
bufs := make([]mtl.Buffer, 0, len(g.unusedBuffers))
|
|
|
|
for b := range g.unusedBuffers {
|
|
|
|
bufs = append(bufs, b)
|
|
|
|
}
|
|
|
|
sort.Slice(bufs, func(a, b int) bool {
|
|
|
|
return bufs[a].Length() > bufs[b].Length()
|
2021-07-06 10:25:11 +02:00
|
|
|
})
|
2021-07-07 17:27:33 +02:00
|
|
|
for _, b := range bufs[maxUnusedBuffers:] {
|
|
|
|
delete(g.unusedBuffers, b)
|
2021-07-06 10:25:11 +02:00
|
|
|
b.Release()
|
|
|
|
}
|
2020-10-12 17:39:45 +02:00
|
|
|
}
|
2021-07-07 19:09:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
|
|
|
|
if g.cb == (mtl.CommandBuffer{}) {
|
|
|
|
g.cb = g.cq.MakeCommandBuffer()
|
|
|
|
}
|
|
|
|
|
|
|
|
var newBuf mtl.Buffer
|
|
|
|
for b := range g.unusedBuffers {
|
|
|
|
if b.Length() >= length {
|
|
|
|
newBuf = b
|
|
|
|
delete(g.unusedBuffers, b)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2021-07-06 10:25:11 +02:00
|
|
|
|
|
|
|
if newBuf == (mtl.Buffer{}) {
|
|
|
|
newBuf = g.view.getMTLDevice().MakeBufferWithLength(pow2(length), resourceStorageMode)
|
|
|
|
}
|
2021-07-07 17:27:33 +02:00
|
|
|
|
2021-07-06 10:25:11 +02:00
|
|
|
if g.buffers == nil {
|
2021-07-07 17:27:33 +02:00
|
|
|
g.buffers = map[mtl.CommandBuffer][]mtl.Buffer{}
|
|
|
|
}
|
|
|
|
if _, ok := g.buffers[g.cb]; !ok {
|
|
|
|
g.cb.Retain()
|
2021-07-06 10:25:11 +02:00
|
|
|
}
|
2021-07-07 17:27:33 +02:00
|
|
|
g.buffers[g.cb] = append(g.buffers[g.cb], newBuf)
|
2021-07-06 10:25:11 +02:00
|
|
|
return newBuf
|
|
|
|
}
|
|
|
|
|
2022-03-21 14:23:07 +01:00
|
|
|
func (g *Graphics) SetVertices(vertices []float32, indices []uint16) error {
|
2021-07-06 10:25:11 +02:00
|
|
|
vbSize := unsafe.Sizeof(vertices[0]) * uintptr(len(vertices))
|
|
|
|
ibSize := unsafe.Sizeof(indices[0]) * uintptr(len(indices))
|
|
|
|
|
|
|
|
g.vb = g.availableBuffer(vbSize)
|
|
|
|
g.vb.CopyToContents(unsafe.Pointer(&vertices[0]), vbSize)
|
|
|
|
|
|
|
|
g.ib = g.availableBuffer(ibSize)
|
|
|
|
g.ib.CopyToContents(unsafe.Pointer(&indices[0]), ibSize)
|
2022-03-21 14:23:07 +01:00
|
|
|
|
|
|
|
return nil
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
2020-11-10 14:48:01 +01:00
|
|
|
func (g *Graphics) flushIfNeeded(present bool) {
|
2020-10-12 17:39:45 +02:00
|
|
|
if g.cb == (mtl.CommandBuffer{}) {
|
|
|
|
return
|
|
|
|
}
|
2021-07-04 11:37:14 +02:00
|
|
|
g.flushRenderCommandEncoderIfNeeded()
|
2018-11-12 16:00:10 +01:00
|
|
|
|
2021-07-07 21:21:20 +02:00
|
|
|
if !g.view.presentsWithTransaction() && present && g.screenDrawable != (ca.MetalDrawable{}) {
|
2020-10-12 17:39:45 +02:00
|
|
|
g.cb.PresentDrawable(g.screenDrawable)
|
|
|
|
}
|
|
|
|
g.cb.Commit()
|
2021-07-07 21:21:20 +02:00
|
|
|
if g.view.presentsWithTransaction() && present && g.screenDrawable != (ca.MetalDrawable{}) {
|
|
|
|
g.cb.WaitUntilScheduled()
|
|
|
|
g.screenDrawable.Present()
|
|
|
|
}
|
2020-11-10 14:48:01 +01:00
|
|
|
|
|
|
|
for _, t := range g.tmpTextures {
|
|
|
|
t.Release()
|
2020-10-12 17:39:45 +02:00
|
|
|
}
|
2021-02-26 18:30:20 +01:00
|
|
|
g.tmpTextures = g.tmpTextures[:0]
|
2020-10-12 19:12:02 +02:00
|
|
|
|
2020-10-12 17:39:45 +02:00
|
|
|
g.cb = mtl.CommandBuffer{}
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
2020-04-04 10:12:24 +02:00
|
|
|
func (g *Graphics) checkSize(width, height int) {
|
2018-11-12 16:00:10 +01:00
|
|
|
if width < 1 {
|
2019-02-14 13:04:34 +01:00
|
|
|
panic(fmt.Sprintf("metal: width (%d) must be equal or more than %d", width, 1))
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
if height < 1 {
|
2019-02-14 13:04:34 +01:00
|
|
|
panic(fmt.Sprintf("metal: height (%d) must be equal or more than %d", height, 1))
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
2020-04-04 10:12:24 +02:00
|
|
|
m := g.MaxImageSize()
|
2018-11-12 16:00:10 +01:00
|
|
|
if width > m {
|
|
|
|
panic(fmt.Sprintf("metal: width (%d) must be less than or equal to %d", width, m))
|
|
|
|
}
|
|
|
|
if height > m {
|
|
|
|
panic(fmt.Sprintf("metal: height (%d) must be less than or equal to %d", height, m))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-06 12:41:32 +01:00
|
|
|
func (g *Graphics) genNextImageID() graphicsdriver.ImageID {
|
2020-05-19 16:48:43 +02:00
|
|
|
g.nextImageID++
|
2021-07-01 07:58:55 +02:00
|
|
|
return g.nextImageID
|
2020-05-19 16:48:43 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 12:41:32 +01:00
|
|
|
func (g *Graphics) genNextShaderID() graphicsdriver.ShaderID {
|
2020-08-03 17:56:45 +02:00
|
|
|
g.nextShaderID++
|
2021-07-01 07:58:55 +02:00
|
|
|
return g.nextShaderID
|
2020-08-03 17:56:45 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 12:41:32 +01:00
|
|
|
func (g *Graphics) NewImage(width, height int) (graphicsdriver.Image, error) {
|
2020-04-04 10:12:24 +02:00
|
|
|
g.checkSize(width, height)
|
2018-11-12 16:00:10 +01:00
|
|
|
td := mtl.TextureDescriptor{
|
2020-06-26 16:36:23 +02:00
|
|
|
TextureType: mtl.TextureType2D,
|
2018-11-12 16:00:10 +01:00
|
|
|
PixelFormat: mtl.PixelFormatRGBA8UNorm,
|
2019-02-14 13:04:34 +01:00
|
|
|
Width: graphics.InternalImageSize(width),
|
|
|
|
Height: graphics.InternalImageSize(height),
|
2019-09-02 21:08:58 +02:00
|
|
|
StorageMode: storageMode,
|
2020-06-26 20:03:53 +02:00
|
|
|
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
2020-10-12 17:39:45 +02:00
|
|
|
t := g.view.getMTLDevice().MakeTexture(td)
|
2020-05-19 16:48:43 +02:00
|
|
|
i := &Image{
|
|
|
|
id: g.genNextImageID(),
|
2020-04-04 10:12:24 +02:00
|
|
|
graphics: g,
|
|
|
|
width: width,
|
|
|
|
height: height,
|
|
|
|
texture: t,
|
2020-05-19 16:48:43 +02:00
|
|
|
}
|
|
|
|
g.addImage(i)
|
|
|
|
return i, nil
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
2022-02-06 12:41:32 +01:00
|
|
|
func (g *Graphics) NewScreenFramebufferImage(width, height int) (graphicsdriver.Image, error) {
|
2020-10-12 17:39:45 +02:00
|
|
|
g.view.setDrawableSize(width, height)
|
2020-05-19 16:48:43 +02:00
|
|
|
i := &Image{
|
|
|
|
id: g.genNextImageID(),
|
2020-04-04 10:12:24 +02:00
|
|
|
graphics: g,
|
|
|
|
width: width,
|
|
|
|
height: height,
|
|
|
|
screen: true,
|
2020-05-19 16:48:43 +02:00
|
|
|
}
|
|
|
|
g.addImage(i)
|
|
|
|
return i, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Graphics) addImage(img *Image) {
|
|
|
|
if g.images == nil {
|
2022-02-06 12:41:32 +01:00
|
|
|
g.images = map[graphicsdriver.ImageID]*Image{}
|
2020-05-19 16:48:43 +02:00
|
|
|
}
|
|
|
|
if _, ok := g.images[img.id]; ok {
|
2022-02-20 14:01:36 +01:00
|
|
|
panic(fmt.Sprintf("metal: image ID %d was already registered", img.id))
|
2020-05-19 16:48:43 +02:00
|
|
|
}
|
|
|
|
g.images[img.id] = img
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Graphics) removeImage(img *Image) {
|
|
|
|
delete(g.images, img.id)
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
2020-04-04 10:12:24 +02:00
|
|
|
func (g *Graphics) SetTransparent(transparent bool) {
|
|
|
|
g.transparent = transparent
|
2019-11-30 16:07:41 +01:00
|
|
|
}
|
|
|
|
|
2022-02-06 12:41:32 +01:00
|
|
|
func operationToBlendFactor(c graphicsdriver.Operation) mtl.BlendFactor {
|
2020-08-03 17:56:45 +02:00
|
|
|
switch c {
|
2022-02-06 12:41:32 +01:00
|
|
|
case graphicsdriver.Zero:
|
2020-08-03 17:56:45 +02:00
|
|
|
return mtl.BlendFactorZero
|
2022-02-06 12:41:32 +01:00
|
|
|
case graphicsdriver.One:
|
2020-08-03 17:56:45 +02:00
|
|
|
return mtl.BlendFactorOne
|
2022-02-06 12:41:32 +01:00
|
|
|
case graphicsdriver.SrcAlpha:
|
2020-08-03 17:56:45 +02:00
|
|
|
return mtl.BlendFactorSourceAlpha
|
2022-02-06 12:41:32 +01:00
|
|
|
case graphicsdriver.DstAlpha:
|
2020-08-03 17:56:45 +02:00
|
|
|
return mtl.BlendFactorDestinationAlpha
|
2022-02-06 12:41:32 +01:00
|
|
|
case graphicsdriver.OneMinusSrcAlpha:
|
2020-08-03 17:56:45 +02:00
|
|
|
return mtl.BlendFactorOneMinusSourceAlpha
|
2022-02-06 12:41:32 +01:00
|
|
|
case graphicsdriver.OneMinusDstAlpha:
|
2020-08-03 17:56:45 +02:00
|
|
|
return mtl.BlendFactorOneMinusDestinationAlpha
|
2022-02-06 12:41:32 +01:00
|
|
|
case graphicsdriver.DstColor:
|
2020-08-03 17:56:45 +02:00
|
|
|
return mtl.BlendFactorDestinationColor
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("metal: invalid operation: %d", c))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-07 06:58:42 +02:00
|
|
|
func (g *Graphics) Initialize() error {
|
2021-07-06 08:05:44 +02:00
|
|
|
// Creating *State objects are expensive and reuse them whenever possible.
|
|
|
|
// See https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/Cmd-Submiss/Cmd-Submiss.html
|
|
|
|
|
2020-10-12 17:39:45 +02:00
|
|
|
// TODO: Release existing rpss
|
|
|
|
if g.rpss == nil {
|
|
|
|
g.rpss = map[rpsKey]mtl.RenderPipelineState{}
|
|
|
|
}
|
2018-11-12 16:00:10 +01:00
|
|
|
|
2021-07-06 08:05:44 +02:00
|
|
|
for _, dss := range g.dsss {
|
|
|
|
dss.Release()
|
|
|
|
}
|
|
|
|
if g.dsss == nil {
|
|
|
|
g.dsss = map[stencilMode]mtl.DepthStencilState{}
|
|
|
|
}
|
|
|
|
|
2022-03-22 17:22:39 +01:00
|
|
|
if err := g.view.initialize(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-10-12 17:39:45 +02:00
|
|
|
if g.transparent {
|
|
|
|
g.view.ml.SetOpaque(false)
|
|
|
|
}
|
2018-11-12 16:00:10 +01:00
|
|
|
|
2020-10-12 17:39:45 +02:00
|
|
|
replaces := map[string]string{
|
2022-02-06 12:41:32 +01:00
|
|
|
"{{.FilterNearest}}": fmt.Sprintf("%d", graphicsdriver.FilterNearest),
|
|
|
|
"{{.FilterLinear}}": fmt.Sprintf("%d", graphicsdriver.FilterLinear),
|
|
|
|
"{{.FilterScreen}}": fmt.Sprintf("%d", graphicsdriver.FilterScreen),
|
|
|
|
"{{.AddressClampToZero}}": fmt.Sprintf("%d", graphicsdriver.AddressClampToZero),
|
|
|
|
"{{.AddressRepeat}}": fmt.Sprintf("%d", graphicsdriver.AddressRepeat),
|
|
|
|
"{{.AddressUnsafe}}": fmt.Sprintf("%d", graphicsdriver.AddressUnsafe),
|
2020-10-12 17:39:45 +02:00
|
|
|
}
|
|
|
|
src := source
|
|
|
|
for k, v := range replaces {
|
|
|
|
src = strings.Replace(src, k, v, -1)
|
|
|
|
}
|
2018-11-12 16:00:10 +01:00
|
|
|
|
2020-10-12 17:39:45 +02:00
|
|
|
lib, err := g.view.getMTLDevice().MakeLibrary(src, mtl.CompileOptions{})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
vs, err := lib.MakeFunction("VertexShader")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fs, err := lib.MakeFunction(
|
2022-02-06 12:41:32 +01:00
|
|
|
fmt.Sprintf("FragmentShader_%d_%d_%d", 0, graphicsdriver.FilterScreen, graphicsdriver.AddressUnsafe))
|
2020-10-12 17:39:45 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
rpld := mtl.RenderPipelineDescriptor{
|
2021-07-06 07:05:01 +02:00
|
|
|
VertexFunction: vs,
|
|
|
|
FragmentFunction: fs,
|
2020-10-12 17:39:45 +02:00
|
|
|
}
|
|
|
|
rpld.ColorAttachments[0].PixelFormat = g.view.colorPixelFormat()
|
|
|
|
rpld.ColorAttachments[0].BlendingEnabled = true
|
|
|
|
rpld.ColorAttachments[0].DestinationAlphaBlendFactor = mtl.BlendFactorZero
|
|
|
|
rpld.ColorAttachments[0].DestinationRGBBlendFactor = mtl.BlendFactorZero
|
|
|
|
rpld.ColorAttachments[0].SourceAlphaBlendFactor = mtl.BlendFactorOne
|
|
|
|
rpld.ColorAttachments[0].SourceRGBBlendFactor = mtl.BlendFactorOne
|
2021-07-02 12:26:09 +02:00
|
|
|
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
|
2020-10-12 17:39:45 +02:00
|
|
|
rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
g.screenRPS = rps
|
|
|
|
|
|
|
|
for _, screen := range []bool{false, true} {
|
|
|
|
for _, cm := range []bool{false, true} {
|
2022-02-06 12:41:32 +01:00
|
|
|
for _, a := range []graphicsdriver.Address{
|
|
|
|
graphicsdriver.AddressClampToZero,
|
|
|
|
graphicsdriver.AddressRepeat,
|
|
|
|
graphicsdriver.AddressUnsafe,
|
2020-10-12 17:39:45 +02:00
|
|
|
} {
|
2022-02-06 12:41:32 +01:00
|
|
|
for _, f := range []graphicsdriver.Filter{
|
|
|
|
graphicsdriver.FilterNearest,
|
|
|
|
graphicsdriver.FilterLinear,
|
2019-02-16 09:08:53 +01:00
|
|
|
} {
|
2022-02-06 12:41:32 +01:00
|
|
|
for c := graphicsdriver.CompositeModeSourceOver; c <= graphicsdriver.CompositeModeMax; c++ {
|
2021-07-06 07:05:01 +02:00
|
|
|
for _, stencil := range []stencilMode{
|
|
|
|
prepareStencil,
|
|
|
|
drawWithStencil,
|
|
|
|
noStencil,
|
|
|
|
} {
|
2021-07-02 12:26:09 +02:00
|
|
|
cmi := 0
|
|
|
|
if cm {
|
|
|
|
cmi = 1
|
|
|
|
}
|
|
|
|
fs, err := lib.MakeFunction(fmt.Sprintf("FragmentShader_%d_%d_%d", cmi, f, a))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
rpld := mtl.RenderPipelineDescriptor{
|
2021-07-06 07:05:01 +02:00
|
|
|
VertexFunction: vs,
|
|
|
|
FragmentFunction: fs,
|
|
|
|
}
|
|
|
|
if stencil != noStencil {
|
|
|
|
rpld.StencilAttachmentPixelFormat = mtl.PixelFormatStencil8
|
2021-07-02 12:26:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pix := mtl.PixelFormatRGBA8UNorm
|
|
|
|
if screen {
|
|
|
|
pix = g.view.colorPixelFormat()
|
|
|
|
}
|
|
|
|
rpld.ColorAttachments[0].PixelFormat = pix
|
|
|
|
rpld.ColorAttachments[0].BlendingEnabled = true
|
|
|
|
|
|
|
|
src, dst := c.Operations()
|
|
|
|
rpld.ColorAttachments[0].DestinationAlphaBlendFactor = operationToBlendFactor(dst)
|
|
|
|
rpld.ColorAttachments[0].DestinationRGBBlendFactor = operationToBlendFactor(dst)
|
|
|
|
rpld.ColorAttachments[0].SourceAlphaBlendFactor = operationToBlendFactor(src)
|
|
|
|
rpld.ColorAttachments[0].SourceRGBBlendFactor = operationToBlendFactor(src)
|
2021-07-06 07:05:01 +02:00
|
|
|
if stencil == prepareStencil {
|
2021-07-02 12:26:09 +02:00
|
|
|
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskNone
|
2021-07-06 07:05:01 +02:00
|
|
|
} else {
|
|
|
|
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
|
2021-07-02 12:26:09 +02:00
|
|
|
}
|
|
|
|
rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
g.rpss[rpsKey{
|
2021-07-06 07:05:01 +02:00
|
|
|
screen: screen,
|
|
|
|
useColorM: cm,
|
|
|
|
filter: f,
|
|
|
|
address: a,
|
|
|
|
compositeMode: c,
|
|
|
|
stencilMode: stencil,
|
2021-07-02 12:26:09 +02:00
|
|
|
}] = rps
|
2019-02-16 09:08:53 +01:00
|
|
|
}
|
2019-02-13 13:46:39 +01:00
|
|
|
}
|
|
|
|
}
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-06 08:05:44 +02:00
|
|
|
// The stencil reference value is always 0 (default).
|
|
|
|
g.dsss[prepareStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
|
|
|
|
BackFaceStencil: mtl.StencilDescriptor{
|
|
|
|
StencilFailureOperation: mtl.StencilOperationKeep,
|
|
|
|
DepthFailureOperation: mtl.StencilOperationKeep,
|
|
|
|
DepthStencilPassOperation: mtl.StencilOperationInvert,
|
|
|
|
StencilCompareFunction: mtl.CompareFunctionAlways,
|
|
|
|
},
|
|
|
|
FrontFaceStencil: mtl.StencilDescriptor{
|
|
|
|
StencilFailureOperation: mtl.StencilOperationKeep,
|
|
|
|
DepthFailureOperation: mtl.StencilOperationKeep,
|
|
|
|
DepthStencilPassOperation: mtl.StencilOperationInvert,
|
|
|
|
StencilCompareFunction: mtl.CompareFunctionAlways,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
g.dsss[drawWithStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
|
|
|
|
BackFaceStencil: mtl.StencilDescriptor{
|
|
|
|
StencilFailureOperation: mtl.StencilOperationKeep,
|
|
|
|
DepthFailureOperation: mtl.StencilOperationKeep,
|
2022-01-05 19:19:46 +01:00
|
|
|
DepthStencilPassOperation: mtl.StencilOperationKeep,
|
2021-07-06 08:05:44 +02:00
|
|
|
StencilCompareFunction: mtl.CompareFunctionNotEqual,
|
|
|
|
},
|
|
|
|
FrontFaceStencil: mtl.StencilDescriptor{
|
|
|
|
StencilFailureOperation: mtl.StencilOperationKeep,
|
|
|
|
DepthFailureOperation: mtl.StencilOperationKeep,
|
2022-01-05 19:19:46 +01:00
|
|
|
DepthStencilPassOperation: mtl.StencilOperationKeep,
|
2021-07-06 08:05:44 +02:00
|
|
|
StencilCompareFunction: mtl.CompareFunctionNotEqual,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
g.dsss[noStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
|
|
|
|
BackFaceStencil: mtl.StencilDescriptor{
|
|
|
|
StencilFailureOperation: mtl.StencilOperationKeep,
|
|
|
|
DepthFailureOperation: mtl.StencilOperationKeep,
|
|
|
|
DepthStencilPassOperation: mtl.StencilOperationKeep,
|
|
|
|
StencilCompareFunction: mtl.CompareFunctionAlways,
|
|
|
|
},
|
|
|
|
FrontFaceStencil: mtl.StencilDescriptor{
|
|
|
|
StencilFailureOperation: mtl.StencilOperationKeep,
|
|
|
|
DepthFailureOperation: mtl.StencilOperationKeep,
|
|
|
|
DepthStencilPassOperation: mtl.StencilOperationKeep,
|
|
|
|
StencilCompareFunction: mtl.CompareFunctionAlways,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2020-10-12 17:39:45 +02:00
|
|
|
g.cq = g.view.getMTLDevice().MakeCommandQueue()
|
2018-11-12 16:00:10 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-07-04 11:13:18 +02:00
|
|
|
func (g *Graphics) flushRenderCommandEncoderIfNeeded() {
|
|
|
|
if g.rce == (mtl.RenderCommandEncoder{}) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
g.rce.EndEncoding()
|
|
|
|
g.rce = mtl.RenderCommandEncoder{}
|
2021-07-07 18:56:12 +02:00
|
|
|
g.lastDst = nil
|
2021-07-04 11:13:18 +02:00
|
|
|
}
|
|
|
|
|
2022-03-12 19:42:10 +01:00
|
|
|
func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion graphicsdriver.Region, srcs [graphics.ShaderImageNum]*Image, indexLen int, indexOffset int, uniforms [][]float32, stencilMode stencilMode) error {
|
2021-07-05 11:08:52 +02:00
|
|
|
// When prepareing a stencil buffer, flush the current render command encoder
|
|
|
|
// to make sure the stencil buffer is cleared when loading.
|
|
|
|
// TODO: What about clearing the stencil buffer by vertices?
|
2021-07-07 18:56:12 +02:00
|
|
|
if g.lastDst != dst || (g.lastStencilMode == noStencil) != (stencilMode == noStencil) || stencilMode == prepareStencil {
|
2021-07-04 11:13:18 +02:00
|
|
|
g.flushRenderCommandEncoderIfNeeded()
|
|
|
|
}
|
2021-07-07 18:56:12 +02:00
|
|
|
g.lastDst = dst
|
2021-07-06 07:05:01 +02:00
|
|
|
g.lastStencilMode = stencilMode
|
2021-07-02 12:26:09 +02:00
|
|
|
|
2021-07-04 11:13:18 +02:00
|
|
|
if g.rce == (mtl.RenderCommandEncoder{}) {
|
|
|
|
rpd := mtl.RenderPassDescriptor{}
|
|
|
|
// Even though the destination pixels are not used, mtl.LoadActionDontCare might cause glitches
|
|
|
|
// (#1019). Always using mtl.LoadActionLoad is safe.
|
2021-07-06 18:43:32 +02:00
|
|
|
if dst.screen {
|
|
|
|
rpd.ColorAttachments[0].LoadAction = mtl.LoadActionClear
|
|
|
|
} else {
|
|
|
|
rpd.ColorAttachments[0].LoadAction = mtl.LoadActionLoad
|
|
|
|
}
|
2021-07-04 11:13:18 +02:00
|
|
|
|
2021-07-08 10:39:33 +02:00
|
|
|
// The store action should always be 'store' even for the screen (#1700).
|
|
|
|
rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore
|
|
|
|
|
2021-07-04 11:13:18 +02:00
|
|
|
t := dst.mtlTexture()
|
|
|
|
if t == (mtl.Texture{}) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
rpd.ColorAttachments[0].Texture = t
|
|
|
|
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{}
|
2020-05-19 16:48:43 +02:00
|
|
|
|
2021-07-05 11:08:52 +02:00
|
|
|
if stencilMode == prepareStencil {
|
2021-07-02 12:26:09 +02:00
|
|
|
dst.ensureStencil()
|
|
|
|
rpd.StencilAttachment.LoadAction = mtl.LoadActionClear
|
|
|
|
rpd.StencilAttachment.StoreAction = mtl.StoreActionDontCare
|
|
|
|
rpd.StencilAttachment.Texture = dst.stencil
|
|
|
|
}
|
|
|
|
|
2021-07-04 11:13:18 +02:00
|
|
|
if g.cb == (mtl.CommandBuffer{}) {
|
|
|
|
g.cb = g.cq.MakeCommandBuffer()
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
2021-07-04 11:13:18 +02:00
|
|
|
g.rce = g.cb.MakeRenderCommandEncoder(rpd)
|
2020-08-07 20:01:23 +02:00
|
|
|
}
|
2018-11-12 16:00:10 +01:00
|
|
|
|
2021-07-04 11:13:18 +02:00
|
|
|
g.rce.SetRenderPipelineState(rps)
|
2020-08-07 20:01:23 +02:00
|
|
|
|
2020-08-08 09:07:03 +02:00
|
|
|
w, h := dst.internalSize()
|
2021-07-04 11:13:18 +02:00
|
|
|
g.rce.SetViewport(mtl.Viewport{
|
2020-08-07 20:01:23 +02:00
|
|
|
OriginX: 0,
|
2022-02-26 17:49:56 +01:00
|
|
|
OriginY: 0,
|
2020-08-07 20:01:23 +02:00
|
|
|
Width: float64(w),
|
2022-02-26 17:49:56 +01:00
|
|
|
Height: float64(h),
|
2020-08-07 20:01:23 +02:00
|
|
|
ZNear: -1,
|
|
|
|
ZFar: 1,
|
|
|
|
})
|
2021-07-04 11:13:18 +02:00
|
|
|
g.rce.SetScissorRect(mtl.ScissorRect{
|
2020-11-07 11:14:06 +01:00
|
|
|
X: int(dstRegion.X),
|
|
|
|
Y: int(dstRegion.Y),
|
|
|
|
Width: int(dstRegion.Width),
|
|
|
|
Height: int(dstRegion.Height),
|
|
|
|
})
|
2021-07-04 11:13:18 +02:00
|
|
|
g.rce.SetVertexBuffer(g.vb, 0, 0)
|
2020-08-07 20:01:23 +02:00
|
|
|
|
|
|
|
for i, u := range uniforms {
|
2022-03-12 19:42:10 +01:00
|
|
|
g.rce.SetVertexBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
|
|
|
|
g.rce.SetFragmentBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
|
2020-08-07 20:01:23 +02:00
|
|
|
}
|
2018-11-12 16:00:10 +01:00
|
|
|
|
2020-08-07 20:01:23 +02:00
|
|
|
for i, src := range srcs {
|
|
|
|
if src != nil {
|
2021-07-04 11:13:18 +02:00
|
|
|
g.rce.SetFragmentTexture(src.texture, i)
|
2018-11-12 16:00:10 +01:00
|
|
|
} else {
|
2021-07-04 11:13:18 +02:00
|
|
|
g.rce.SetFragmentTexture(mtl.Texture{}, i)
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
2020-08-07 20:01:23 +02:00
|
|
|
}
|
2021-07-02 12:26:09 +02:00
|
|
|
|
2021-07-06 08:05:44 +02:00
|
|
|
g.rce.SetDepthStencilState(g.dsss[stencilMode])
|
2021-07-02 12:26:09 +02:00
|
|
|
|
2021-07-04 11:13:18 +02:00
|
|
|
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, indexLen, mtl.IndexTypeUInt16, g.ib, indexOffset*2)
|
2021-07-02 12:26:09 +02:00
|
|
|
|
2020-08-07 20:01:23 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-03-12 19:42:10 +01:00
|
|
|
func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderImageNum]graphicsdriver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID graphicsdriver.ShaderID, indexLen int, indexOffset int, mode graphicsdriver.CompositeMode, colorM graphicsdriver.ColorM, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, uniforms [][]float32, evenOdd bool) error {
|
2020-08-07 20:01:23 +02:00
|
|
|
dst := g.images[dstID]
|
2020-11-03 18:45:41 +01:00
|
|
|
|
2021-07-04 10:24:39 +02:00
|
|
|
if dst.screen {
|
|
|
|
g.view.update()
|
|
|
|
}
|
|
|
|
|
2021-07-01 17:25:09 +02:00
|
|
|
var srcs [graphics.ShaderImageNum]*Image
|
|
|
|
for i, srcID := range srcIDs {
|
|
|
|
srcs[i] = g.images[srcID]
|
|
|
|
}
|
2020-08-07 20:01:23 +02:00
|
|
|
|
2021-07-06 07:05:01 +02:00
|
|
|
rpss := map[stencilMode]mtl.RenderPipelineState{}
|
2022-03-12 19:42:10 +01:00
|
|
|
var uniformVars [][]float32
|
2022-02-06 12:41:32 +01:00
|
|
|
if shaderID == graphicsdriver.InvalidShaderID {
|
|
|
|
if dst.screen && filter == graphicsdriver.FilterScreen {
|
2021-07-06 07:05:01 +02:00
|
|
|
rpss[noStencil] = g.screenRPS
|
2021-07-01 17:25:09 +02:00
|
|
|
} else {
|
2021-07-06 07:05:01 +02:00
|
|
|
for _, stencil := range []stencilMode{
|
|
|
|
prepareStencil,
|
|
|
|
drawWithStencil,
|
|
|
|
noStencil,
|
|
|
|
} {
|
|
|
|
rpss[stencil] = g.rpss[rpsKey{
|
|
|
|
screen: dst.screen,
|
2021-07-27 03:37:14 +02:00
|
|
|
useColorM: !colorM.IsIdentity(),
|
2021-07-06 07:05:01 +02:00
|
|
|
filter: filter,
|
|
|
|
address: address,
|
|
|
|
compositeMode: mode,
|
|
|
|
stencilMode: stencil,
|
|
|
|
}]
|
|
|
|
}
|
2021-07-01 17:25:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
w, h := dst.internalSize()
|
|
|
|
sourceSize := []float32{0, 0}
|
2022-02-06 12:41:32 +01:00
|
|
|
if filter != graphicsdriver.FilterNearest {
|
2021-07-01 17:25:09 +02:00
|
|
|
w, h := srcs[0].internalSize()
|
|
|
|
sourceSize[0] = float32(w)
|
|
|
|
sourceSize[1] = float32(h)
|
|
|
|
}
|
2021-09-04 10:29:42 +02:00
|
|
|
var esBody [16]float32
|
|
|
|
var esTranslate [4]float32
|
2021-09-04 11:08:23 +02:00
|
|
|
colorM.Elements(&esBody, &esTranslate)
|
2021-07-01 17:25:09 +02:00
|
|
|
scale := float32(0)
|
2022-02-06 12:41:32 +01:00
|
|
|
if filter == graphicsdriver.FilterScreen {
|
2021-07-01 17:25:09 +02:00
|
|
|
scale = float32(dst.width) / float32(srcs[0].width)
|
|
|
|
}
|
2022-03-12 19:42:10 +01:00
|
|
|
uniformVars = [][]float32{
|
|
|
|
{float32(w), float32(h)},
|
|
|
|
sourceSize,
|
|
|
|
esBody[:],
|
|
|
|
esTranslate[:],
|
2021-10-29 19:51:02 +02:00
|
|
|
{
|
2022-03-12 19:42:10 +01:00
|
|
|
srcRegion.X,
|
|
|
|
srcRegion.Y,
|
|
|
|
srcRegion.X + srcRegion.Width,
|
|
|
|
srcRegion.Y + srcRegion.Height,
|
2022-03-05 14:12:05 +01:00
|
|
|
},
|
2022-03-12 19:42:10 +01:00
|
|
|
{scale},
|
2021-07-01 17:25:09 +02:00
|
|
|
}
|
2020-08-07 20:01:23 +02:00
|
|
|
} else {
|
2021-07-06 07:05:01 +02:00
|
|
|
for _, stencil := range []stencilMode{
|
|
|
|
prepareStencil,
|
|
|
|
drawWithStencil,
|
|
|
|
noStencil,
|
|
|
|
} {
|
|
|
|
var err error
|
|
|
|
rpss[stencil], err = g.shaders[shaderID].RenderPipelineState(g.view.getMTLDevice(), mode, stencil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-07-01 17:25:09 +02:00
|
|
|
}
|
|
|
|
|
2022-03-12 19:42:10 +01:00
|
|
|
uniformVars = make([][]float32, graphics.PreservedUniformVariablesNum+len(uniforms))
|
2021-07-01 17:25:09 +02:00
|
|
|
|
|
|
|
// Set the destination texture size.
|
|
|
|
dw, dh := dst.internalSize()
|
2022-03-12 19:42:10 +01:00
|
|
|
uniformVars[graphics.TextureDestinationSizeUniformVariableIndex] = []float32{float32(dw), float32(dh)}
|
2021-07-01 17:25:09 +02:00
|
|
|
|
|
|
|
// Set the source texture sizes.
|
|
|
|
usizes := make([]float32, 2*len(srcs))
|
|
|
|
for i, src := range srcs {
|
|
|
|
if src != nil {
|
|
|
|
w, h := src.internalSize()
|
|
|
|
usizes[2*i] = float32(w)
|
|
|
|
usizes[2*i+1] = float32(h)
|
|
|
|
}
|
|
|
|
}
|
2022-03-12 19:42:10 +01:00
|
|
|
uniformVars[graphics.TextureSourceSizesUniformVariableIndex] = usizes
|
2021-07-01 17:25:09 +02:00
|
|
|
|
|
|
|
// Set the destination region's origin.
|
|
|
|
udorigin := []float32{float32(dstRegion.X) / float32(dw), float32(dstRegion.Y) / float32(dh)}
|
2022-03-12 19:42:10 +01:00
|
|
|
uniformVars[graphics.TextureDestinationRegionOriginUniformVariableIndex] = udorigin
|
2021-07-01 17:25:09 +02:00
|
|
|
|
|
|
|
// Set the destination region's size.
|
|
|
|
udsize := []float32{float32(dstRegion.Width) / float32(dw), float32(dstRegion.Height) / float32(dh)}
|
2022-03-12 19:42:10 +01:00
|
|
|
uniformVars[graphics.TextureDestinationRegionSizeUniformVariableIndex] = udsize
|
2021-07-01 17:25:09 +02:00
|
|
|
|
|
|
|
// Set the source offsets.
|
|
|
|
uoffsets := make([]float32, 2*len(offsets))
|
|
|
|
for i, offset := range offsets {
|
|
|
|
uoffsets[2*i] = offset[0]
|
|
|
|
uoffsets[2*i+1] = offset[1]
|
|
|
|
}
|
2022-03-12 19:42:10 +01:00
|
|
|
uniformVars[graphics.TextureSourceOffsetsUniformVariableIndex] = uoffsets
|
2021-07-01 17:25:09 +02:00
|
|
|
|
|
|
|
// Set the source region's origin of texture0.
|
|
|
|
usorigin := []float32{float32(srcRegion.X), float32(srcRegion.Y)}
|
2022-03-12 19:42:10 +01:00
|
|
|
uniformVars[graphics.TextureSourceRegionOriginUniformVariableIndex] = usorigin
|
2021-07-01 17:25:09 +02:00
|
|
|
|
|
|
|
// Set the source region's size of texture0.
|
|
|
|
ussize := []float32{float32(srcRegion.Width), float32(srcRegion.Height)}
|
2022-03-12 19:42:10 +01:00
|
|
|
uniformVars[graphics.TextureSourceRegionSizeUniformVariableIndex] = ussize
|
2021-07-01 17:25:09 +02:00
|
|
|
|
|
|
|
// Set the additional uniform variables.
|
|
|
|
for i, v := range uniforms {
|
|
|
|
const offset = graphics.PreservedUniformVariablesNum
|
2022-03-26 16:12:10 +01:00
|
|
|
t := g.shaders[shaderID].ir.Uniforms[offset+i]
|
|
|
|
switch t.Main {
|
2022-03-26 15:46:37 +01:00
|
|
|
case shaderir.Mat3:
|
|
|
|
// float3x3 requires 16-byte alignment (#2036).
|
2022-03-26 16:12:10 +01:00
|
|
|
v1 := make([]float32, 12)
|
|
|
|
copy(v1[0:3], v[0:3])
|
|
|
|
copy(v1[4:7], v[3:6])
|
|
|
|
copy(v1[8:11], v[6:9])
|
|
|
|
uniformVars[offset+i] = v1
|
|
|
|
case shaderir.Array:
|
|
|
|
switch t.Sub[0].Main {
|
|
|
|
case shaderir.Mat3:
|
|
|
|
v1 := make([]float32, t.Length*12)
|
|
|
|
for j := 0; j < t.Length; j++ {
|
|
|
|
offset0 := j * 9
|
|
|
|
offset1 := j * 12
|
|
|
|
copy(v1[offset1:offset1+3], v[offset0:offset0+3])
|
|
|
|
copy(v1[offset1+4:offset1+7], v[offset0+3:offset0+6])
|
|
|
|
copy(v1[offset1+8:offset1+11], v[offset0+6:offset0+9])
|
|
|
|
}
|
|
|
|
uniformVars[offset+i] = v1
|
|
|
|
default:
|
|
|
|
uniformVars[offset+i] = v
|
|
|
|
}
|
2022-03-26 15:46:37 +01:00
|
|
|
default:
|
|
|
|
uniformVars[offset+i] = v
|
|
|
|
}
|
2021-07-01 17:25:09 +02:00
|
|
|
}
|
2020-08-07 20:01:23 +02:00
|
|
|
}
|
|
|
|
|
2021-07-02 12:26:09 +02:00
|
|
|
if evenOdd {
|
2021-07-06 07:05:01 +02:00
|
|
|
if err := g.draw(rpss[prepareStencil], dst, dstRegion, srcs, indexLen, indexOffset, uniformVars, prepareStencil); err != nil {
|
2021-07-02 12:26:09 +02:00
|
|
|
return err
|
|
|
|
}
|
2021-07-06 07:05:01 +02:00
|
|
|
if err := g.draw(rpss[drawWithStencil], dst, dstRegion, srcs, indexLen, indexOffset, uniformVars, drawWithStencil); err != nil {
|
2021-07-02 12:26:09 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
2021-07-06 07:05:01 +02:00
|
|
|
if err := g.draw(rpss[noStencil], dst, dstRegion, srcs, indexLen, indexOffset, uniformVars, noStencil); err != nil {
|
2021-07-02 12:26:09 +02:00
|
|
|
return err
|
|
|
|
}
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
2021-07-04 11:13:18 +02:00
|
|
|
|
2018-11-12 16:00:10 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-04 10:12:24 +02:00
|
|
|
func (g *Graphics) SetVsyncEnabled(enabled bool) {
|
|
|
|
g.view.setDisplaySyncEnabled(enabled)
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
2021-08-08 08:38:41 +02:00
|
|
|
func (g *Graphics) SetFullscreen(fullscreen bool) {
|
|
|
|
g.view.setFullscreen(fullscreen)
|
|
|
|
}
|
|
|
|
|
2022-02-06 12:41:32 +01:00
|
|
|
func (g *Graphics) FramebufferYDirection() graphicsdriver.YDirection {
|
|
|
|
return graphicsdriver.Downward
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
2022-02-26 17:49:56 +01:00
|
|
|
func (g *Graphics) NDCYDirection() graphicsdriver.YDirection {
|
|
|
|
return graphicsdriver.Upward
|
|
|
|
}
|
|
|
|
|
2020-04-04 10:12:24 +02:00
|
|
|
func (g *Graphics) NeedsRestoring() bool {
|
2019-05-26 12:08:46 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-07-06 18:43:32 +02:00
|
|
|
func (g *Graphics) NeedsClearingScreen() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-04-04 10:12:24 +02:00
|
|
|
func (g *Graphics) IsGL() bool {
|
2018-11-12 16:00:10 +01:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-02-11 12:38:45 +01:00
|
|
|
func (g *Graphics) IsDirectX() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-04-04 10:12:24 +02:00
|
|
|
func (g *Graphics) HasHighPrecisionFloat() bool {
|
2019-06-21 21:47:48 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-04-04 10:12:24 +02:00
|
|
|
func (g *Graphics) MaxImageSize() int {
|
2021-07-04 13:35:56 +02:00
|
|
|
if g.maxImageSize != 0 {
|
|
|
|
return g.maxImageSize
|
|
|
|
}
|
|
|
|
|
|
|
|
g.maxImageSize = 4096
|
|
|
|
// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
|
|
|
|
switch {
|
|
|
|
case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily5_v1):
|
|
|
|
g.maxImageSize = 16384
|
|
|
|
case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily4_v1):
|
|
|
|
g.maxImageSize = 16384
|
|
|
|
case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily3_v1):
|
|
|
|
g.maxImageSize = 16384
|
|
|
|
case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily2_v2):
|
|
|
|
g.maxImageSize = 8192
|
|
|
|
case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily2_v1):
|
2020-10-12 17:39:45 +02:00
|
|
|
g.maxImageSize = 4096
|
2021-07-04 13:35:56 +02:00
|
|
|
case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily1_v2):
|
|
|
|
g.maxImageSize = 8192
|
|
|
|
case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily1_v1):
|
|
|
|
g.maxImageSize = 4096
|
|
|
|
case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_tvOS_GPUFamily2_v1):
|
|
|
|
g.maxImageSize = 16384
|
|
|
|
case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_tvOS_GPUFamily1_v1):
|
|
|
|
g.maxImageSize = 8192
|
|
|
|
case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_macOS_GPUFamily1_v1):
|
|
|
|
g.maxImageSize = 16384
|
|
|
|
default:
|
|
|
|
panic("metal: there is no supported feature set")
|
2020-10-12 17:39:45 +02:00
|
|
|
}
|
|
|
|
return g.maxImageSize
|
2019-07-03 18:10:08 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 12:41:32 +01:00
|
|
|
func (g *Graphics) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) {
|
2020-10-12 17:39:45 +02:00
|
|
|
s, err := newShader(g.view.getMTLDevice(), g.genNextShaderID(), program)
|
|
|
|
if err != nil {
|
2020-08-03 17:56:45 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
g.addShader(s)
|
|
|
|
return s, nil
|
2020-05-17 20:45:58 +02:00
|
|
|
}
|
|
|
|
|
2020-08-03 17:56:45 +02:00
|
|
|
func (g *Graphics) addShader(shader *Shader) {
|
|
|
|
if g.shaders == nil {
|
2022-02-06 12:41:32 +01:00
|
|
|
g.shaders = map[graphicsdriver.ShaderID]*Shader{}
|
2020-08-03 17:56:45 +02:00
|
|
|
}
|
|
|
|
if _, ok := g.shaders[shader.id]; ok {
|
|
|
|
panic(fmt.Sprintf("metal: shader ID %d was already registered", shader.id))
|
|
|
|
}
|
|
|
|
g.shaders[shader.id] = shader
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Graphics) removeShader(shader *Shader) {
|
|
|
|
delete(g.shaders, shader.id)
|
2020-05-17 20:45:58 +02:00
|
|
|
}
|
|
|
|
|
2018-11-12 16:00:10 +01:00
|
|
|
type Image struct {
|
2022-02-06 12:41:32 +01:00
|
|
|
id graphicsdriver.ImageID
|
2020-04-04 10:12:24 +02:00
|
|
|
graphics *Graphics
|
|
|
|
width int
|
|
|
|
height int
|
|
|
|
screen bool
|
|
|
|
texture mtl.Texture
|
2021-07-02 12:26:09 +02:00
|
|
|
stencil mtl.Texture
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
2022-02-06 12:41:32 +01:00
|
|
|
func (i *Image) ID() graphicsdriver.ImageID {
|
2020-05-19 16:48:43 +02:00
|
|
|
return i.id
|
|
|
|
}
|
|
|
|
|
2020-08-08 09:07:03 +02:00
|
|
|
func (i *Image) internalSize() (int, int) {
|
2018-11-12 16:00:10 +01:00
|
|
|
if i.screen {
|
|
|
|
return i.width, i.height
|
|
|
|
}
|
2019-02-14 13:04:34 +01:00
|
|
|
return graphics.InternalImageSize(i.width), graphics.InternalImageSize(i.height)
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *Image) Dispose() {
|
2021-07-02 12:26:09 +02:00
|
|
|
if i.stencil != (mtl.Texture{}) {
|
|
|
|
i.stencil.Release()
|
|
|
|
i.stencil = mtl.Texture{}
|
|
|
|
}
|
2020-10-12 17:39:45 +02:00
|
|
|
if i.texture != (mtl.Texture{}) {
|
|
|
|
i.texture.Release()
|
|
|
|
i.texture = mtl.Texture{}
|
|
|
|
}
|
2020-05-19 16:48:43 +02:00
|
|
|
i.graphics.removeImage(i)
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *Image) IsInvalidated() bool {
|
|
|
|
// TODO: Does Metal cause context lost?
|
|
|
|
// https://developer.apple.com/documentation/metal/mtlresource/1515898-setpurgeablestate
|
|
|
|
// https://developer.apple.com/documentation/metal/mtldevicenotificationhandler
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-02-26 15:00:10 +01:00
|
|
|
func (i *Image) syncTexture() {
|
2021-07-04 11:13:18 +02:00
|
|
|
i.graphics.flushRenderCommandEncoderIfNeeded()
|
|
|
|
|
2020-11-03 15:44:40 +01:00
|
|
|
// Calling SynchronizeTexture is ignored on iOS (see mtl.m), but it looks like committing BlitCommandEncoder
|
2020-09-06 09:51:44 +02:00
|
|
|
// is necessary (#1337).
|
2020-10-12 17:39:45 +02:00
|
|
|
if i.graphics.cb != (mtl.CommandBuffer{}) {
|
|
|
|
panic("metal: command buffer must be empty at syncTexture: flushIfNeeded is not called yet?")
|
|
|
|
}
|
2018-11-12 16:00:10 +01:00
|
|
|
|
2020-10-12 17:39:45 +02:00
|
|
|
cb := i.graphics.cq.MakeCommandBuffer()
|
|
|
|
bce := cb.MakeBlitCommandEncoder()
|
|
|
|
bce.SynchronizeTexture(i.texture, 0, 0)
|
|
|
|
bce.EndEncoding()
|
2020-11-03 18:45:41 +01:00
|
|
|
|
2020-10-12 17:39:45 +02:00
|
|
|
cb.Commit()
|
2022-03-14 14:02:44 +01:00
|
|
|
// TODO: Are fences available here?
|
2021-02-26 15:00:10 +01:00
|
|
|
cb.WaitUntilCompleted()
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
2022-02-27 09:41:19 +01:00
|
|
|
func (i *Image) ReadPixels(buf []byte) error {
|
|
|
|
if got, want := len(buf), 4*i.width*i.height; got != want {
|
|
|
|
return fmt.Errorf("metal: len(buf) must be %d but %d at ReadPixels", want, got)
|
|
|
|
}
|
|
|
|
|
2021-02-26 15:00:10 +01:00
|
|
|
i.graphics.flushIfNeeded(false)
|
|
|
|
i.syncTexture()
|
2018-11-12 16:00:10 +01:00
|
|
|
|
2022-02-27 09:41:19 +01:00
|
|
|
i.texture.GetBytes(&buf[0], uintptr(4*i.width), mtl.Region{
|
2020-10-12 17:39:45 +02:00
|
|
|
Size: mtl.Size{Width: i.width, Height: i.height, Depth: 1},
|
|
|
|
}, 0)
|
2022-02-27 09:41:19 +01:00
|
|
|
return nil
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
|
|
|
|
2022-03-21 14:23:07 +01:00
|
|
|
func (i *Image) ReplacePixels(args []*graphicsdriver.ReplacePixelsArgs) error {
|
2020-04-04 10:12:24 +02:00
|
|
|
g := i.graphics
|
2020-10-12 17:39:45 +02:00
|
|
|
|
2021-07-04 11:13:18 +02:00
|
|
|
g.flushRenderCommandEncoderIfNeeded()
|
|
|
|
|
2021-02-26 19:40:36 +01:00
|
|
|
// Calculate the smallest texture size to include all the values in args.
|
|
|
|
minX := math.MaxInt32
|
|
|
|
minY := math.MaxInt32
|
|
|
|
maxX := 0
|
|
|
|
maxY := 0
|
|
|
|
for _, a := range args {
|
|
|
|
if minX > a.X {
|
|
|
|
minX = a.X
|
|
|
|
}
|
|
|
|
if maxX < a.X+a.Width {
|
|
|
|
maxX = a.X + a.Width
|
|
|
|
}
|
|
|
|
if minY > a.Y {
|
|
|
|
minY = a.Y
|
|
|
|
}
|
|
|
|
if maxY < a.Y+a.Height {
|
|
|
|
maxY = a.Y + a.Height
|
|
|
|
}
|
|
|
|
}
|
|
|
|
w := maxX - minX
|
|
|
|
h := maxY - minY
|
|
|
|
|
2020-11-10 14:48:01 +01:00
|
|
|
// Use a temporary texture to send pixels asynchrounsly, whichever the memory is shared (e.g., iOS) or
|
2021-02-26 20:03:59 +01:00
|
|
|
// managed (e.g., macOS). A temporary texture is needed since ReplaceRegion tries to sync the pixel
|
|
|
|
// data between CPU and GPU, and doing it on the existing texture is inefficient (#1418).
|
2021-02-26 19:40:36 +01:00
|
|
|
// The texture cannot be reused until sending the pixels finishes, then create new ones for each call.
|
2020-11-10 14:48:01 +01:00
|
|
|
td := mtl.TextureDescriptor{
|
|
|
|
TextureType: mtl.TextureType2D,
|
|
|
|
PixelFormat: mtl.PixelFormatRGBA8UNorm,
|
|
|
|
Width: w,
|
|
|
|
Height: h,
|
|
|
|
StorageMode: storageMode,
|
|
|
|
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
|
2020-10-12 17:39:45 +02:00
|
|
|
}
|
2020-11-10 14:48:01 +01:00
|
|
|
t := g.view.getMTLDevice().MakeTexture(td)
|
|
|
|
g.tmpTextures = append(g.tmpTextures, t)
|
2020-10-12 19:12:02 +02:00
|
|
|
|
2020-10-12 17:39:45 +02:00
|
|
|
for _, a := range args {
|
2020-11-10 14:48:01 +01:00
|
|
|
t.ReplaceRegion(mtl.Region{
|
2021-02-26 19:40:36 +01:00
|
|
|
Origin: mtl.Origin{X: a.X - minX, Y: a.Y - minY, Z: 0},
|
2020-10-12 17:39:45 +02:00
|
|
|
Size: mtl.Size{Width: a.Width, Height: a.Height, Depth: 1},
|
|
|
|
}, 0, unsafe.Pointer(&a.Pixels[0]), 4*a.Width)
|
|
|
|
}
|
|
|
|
|
|
|
|
if g.cb == (mtl.CommandBuffer{}) {
|
|
|
|
g.cb = i.graphics.cq.MakeCommandBuffer()
|
|
|
|
}
|
|
|
|
bce := g.cb.MakeBlitCommandEncoder()
|
|
|
|
for _, a := range args {
|
2021-02-26 19:40:36 +01:00
|
|
|
so := mtl.Origin{X: a.X - minX, Y: a.Y - minY, Z: 0}
|
|
|
|
ss := mtl.Size{Width: a.Width, Height: a.Height, Depth: 1}
|
|
|
|
do := mtl.Origin{X: a.X, Y: a.Y, Z: 0}
|
|
|
|
bce.CopyFromTexture(t, 0, 0, so, ss, i.texture, 0, 0, do)
|
2020-10-12 17:39:45 +02:00
|
|
|
}
|
|
|
|
bce.EndEncoding()
|
2022-03-21 14:23:07 +01:00
|
|
|
|
|
|
|
return nil
|
2018-11-12 16:00:10 +01:00
|
|
|
}
|
2021-07-04 11:13:18 +02:00
|
|
|
|
|
|
|
func (i *Image) mtlTexture() mtl.Texture {
|
|
|
|
if i.screen {
|
|
|
|
g := i.graphics
|
|
|
|
if g.screenDrawable == (ca.MetalDrawable{}) {
|
2021-07-06 17:38:50 +02:00
|
|
|
drawable := g.view.nextDrawable()
|
2021-07-04 11:13:18 +02:00
|
|
|
if drawable == (ca.MetalDrawable{}) {
|
|
|
|
return mtl.Texture{}
|
|
|
|
}
|
|
|
|
g.screenDrawable = drawable
|
2021-07-07 19:09:04 +02:00
|
|
|
// After nextDrawable, it is expected some command buffers are completed.
|
|
|
|
g.gcBuffers()
|
2021-07-04 11:13:18 +02:00
|
|
|
}
|
|
|
|
return g.screenDrawable.Texture()
|
|
|
|
}
|
|
|
|
return i.texture
|
|
|
|
}
|
2021-07-02 12:26:09 +02:00
|
|
|
|
|
|
|
func (i *Image) ensureStencil() {
|
|
|
|
if i.stencil != (mtl.Texture{}) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
td := mtl.TextureDescriptor{
|
|
|
|
TextureType: mtl.TextureType2D,
|
|
|
|
PixelFormat: mtl.PixelFormatStencil8,
|
|
|
|
Width: graphics.InternalImageSize(i.width),
|
|
|
|
Height: graphics.InternalImageSize(i.height),
|
|
|
|
StorageMode: mtl.StorageModePrivate,
|
|
|
|
Usage: mtl.TextureUsageRenderTarget,
|
|
|
|
}
|
|
|
|
i.stencil = i.graphics.view.getMTLDevice().MakeTexture(td)
|
|
|
|
}
|