Files
sanspie a6822156cd
ci-platforms / windows amd64 smoke (push) Skipped
ci / release (push) Successful in 8m58s
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / darwin arm64 smoke (push) Skipped
ci / checks (push) Successful in 4m46s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 13m28s
Finish Stage 12 editor fixes and refreshed platformer
2026-11-05 17:36:42 +03:00

539 lines
21 KiB
Go

//go:build js && wasm
// This executable uses an actual browser context. A missing browser capability
// is reported as unavailable or failed, never as a successful rendering check.
package main
import (
"encoding/json"
"fmt"
"math"
"syscall/js"
"time"
"aego/core/mathx"
"aego/gpu"
"aego/gpu/webgl2"
"aego/platform"
"aego/platform/web"
)
type surface struct{ ctx js.Value }
func (s surface) WebGL2() js.Value { return s.ctx }
type report struct {
Passed []string `json:"passed"`
Failures []string `json:"failures"`
Unverified []string `json:"unverified"`
Renderer string `json:"renderer"`
Restores int `json:"contextRestores"`
FloatExtension bool `json:"floatExtension"`
FloatRenderable bool `json:"rgba16fRenderable"`
Parallel bool `json:"parallelShaderCompile"`
MaxFragmentTextureUnits int `json:"maxFragmentTextureUnits"`
MaxArrayTextureLayers int `json:"maxArrayTextureLayers"`
}
func (r *report) check(name string, ok bool, detail any) {
if ok {
r.Passed = append(r.Passed, name)
} else {
r.Failures = append(r.Failures, fmt.Sprintf("%s: %v", name, detail))
}
}
func waitFor(fn func() bool) bool {
until := time.Now().Add(15 * time.Second)
for time.Now().Before(until) {
if fn() {
return true
}
time.Sleep(10 * time.Millisecond)
}
return false
}
func source() gpu.ShaderDesc {
return gpu.ShaderDesc{Label: "array sampler", Sources: []gpu.ShaderSource{
{Stage: gpu.StageVertex, Source: `#version 300 es
void main(){vec2 p=vec2((gl_VertexID<<1)&2,gl_VertexID&2);gl_Position=vec4(p*2.0-1.0,0,1);}`},
{Stage: gpu.StageFragment, Source: `#version 300 es
precision highp float;
precision highp sampler2DArray;
uniform sampler2DArray tex3;
out vec4 color;
void main(){color=texelFetch(tex3,ivec3(0,0,1),0);}`},
}}
}
func arrayLayers(r *report, d *webgl2.Device) {
h, err := d.CreateTexture(gpu.TextureDesc{Width: 8, Height: 4, Dimension: gpu.Texture2DArray, Layers: 3, Mips: true, Format: gpu.TexRGBA8})
r.check("array-create", err == nil, err)
if err != nil {
return
}
defer d.DestroyTexture(h)
colors := []mathx.Color{{R: 1, A: 1}, {G: 1, A: 1}, {B: 1, A: 1}}
for layer, c := range colors {
for mip := 0; mip < 4; mip++ {
p := d.BeginFrame(32, 32).BeginPass(gpu.PassDesc{Target: h, TargetSubresource: gpu.TextureSubresource{Layer: layer, Mip: mip}, Color: gpu.LoadClear, ClearColor: c})
p.End()
d.EndFrame()
}
}
for layer := range colors {
for mip := 0; mip < 4; mip++ {
var got [4]byte
err := d.ReadTexturePixels(h, gpu.TextureSubresource{Layer: layer, Mip: mip}, 0, 0, 1, 1, got[:])
want := [4]byte{0, 0, 0, 255}
want[layer] = 255
r.check(fmt.Sprintf("array-layer-%d-mip-%d", layer, mip), err == nil && got == want, fmt.Sprintf("%v, %v", got, err))
}
}
err = d.UpdateTextureRegion(h, gpu.TextureUpdate{Subresource: gpu.TextureSubresource{Layer: 2, Mip: 1}, X: 1, Y: 0, Width: 1, Height: 1, Data: []byte{255, 255, 0, 255}})
var got [8]byte
readErr := d.ReadTexturePixels(h, gpu.TextureSubresource{Layer: 2, Mip: 1}, 0, 0, 2, 1, got[:])
r.check("array-partial-mip-update", err == nil && readErr == nil && got == [8]byte{0, 0, 255, 255, 255, 255, 0, 255}, fmt.Sprintf("%v %v %v", got, err, readErr))
r.check("reject-array-layer-overflow", d.ValidateRenderTarget(gpu.PassDesc{Target: h, TargetSubresource: gpu.TextureSubresource{Layer: 3}}) != nil, "out-of-range layer accepted")
r.check("reject-array-mip-overflow", d.ValidateRenderTarget(gpu.PassDesc{Target: h, TargetSubresource: gpu.TextureSubresource{Mip: 4}}) != nil, "out-of-range mip accepted")
r.check("reject-mip-read-overflow", d.ReadTexturePixels(h, gpu.TextureSubresource{Mip: 3}, 0, 0, 2, 1, make([]byte, 8)) != nil, "read exceeds mip dimensions")
checkClearRect(r, d, h)
}
// With a viewport smaller than the physical 4x2 array mip, prime a nonempty scissor before each clear; the full-mip and unrestricted clears must use identical commands.
func checkClearRect(r *report, d *webgl2.Device, h gpu.TextureHandle) {
sub := gpu.TextureSubresource{Layer: 1, Mip: 1}
full, partial := mathx.Rect{W: 4, H: 2}, mathx.Rect{W: 2, H: 1}
red, green := mathx.Color{R: 1, A: 1}, mathx.Color{G: 1, A: 1}
clear := func(rect mathx.Rect, color mathx.Color) uint64 {
f := d.BeginFrame(32, 32)
desc := gpu.PassDesc{Target: h, TargetSubresource: sub, Color: gpu.LoadKeep}
p := f.BeginPass(desc)
p.SetViewport(partial)
p.SetScissor(mathx.Rect{X: 2, Y: 1, W: 1, H: 1}, true)
p.End()
desc.Color, desc.ClearColor, desc.ClearRect = gpu.LoadClear, color, rect
before := d.BridgeStats().Calls
p = f.BeginPass(desc)
p.End()
count := d.BridgeStats().Calls - before
d.EndFrame()
return count
}
unrestricted := clear(mathx.Rect{}, green)
explicit := clear(full, red)
r.check("full-clear-no-redundant-scissor", explicit == unrestricted, fmt.Sprintf("full=%d unrestricted=%d", explicit, unrestricted))
clear(partial, green)
var pixels [32]byte
err := d.ReadTexturePixels(h, sub, 0, 0, 4, 2, pixels[:])
correct := err == nil
for i := 0; i < 8; i++ {
want := [4]byte{255, 0, 0, 255}
if i < 2 {
want = [4]byte{0, 255, 0, 255}
}
correct = correct && [4]byte(pixels[i*4:i*4+4]) == want
}
r.check("partial-clear-physical-mip-extent", correct, fmt.Sprintf("%v %v", pixels, err))
draw := source()
draw.Sources[1].Source = "#version 300 es\nprecision highp float; out vec4 color; void main(){color=vec4(0,0,1,1);}"
sh, err := d.CreateShader(draw)
if err != nil {
r.check("draw-after-partial-clear", false, err)
return
}
defer d.DestroyShader(sh)
pipe, err := d.CreatePipeline(gpu.PipelineDesc{Shader: sh, Primitive: gpu.PrimTriangles})
if err != nil {
r.check("draw-after-partial-clear", false, err)
return
}
defer d.DestroyPipeline(pipe)
p := d.BeginFrame(32, 32).BeginPass(gpu.PassDesc{Target: h, TargetSubresource: sub, Color: gpu.LoadKeep})
p.SetPipeline(pipe)
p.Draw(0, 3)
p.End()
d.EndFrame()
err = d.ReadTexturePixels(h, sub, 0, 0, 4, 2, pixels[:])
correct = err == nil
for i := 0; i < 8; i++ {
correct = correct && [4]byte(pixels[i*4:i*4+4]) == [4]byte{0, 0, 255, 255}
}
var sibling [4]byte
e2 := d.ReadTexturePixels(h, gpu.TextureSubresource{Layer: 0, Mip: 1}, 0, 0, 1, 1, sibling[:])
r.check("draw-after-partial-clear", correct && e2 == nil && sibling == [4]byte{255, 0, 0, 255}, fmt.Sprintf("%v sibling=%v %v %v", pixels, sibling, err, e2))
}
// A legacy flat 2D pass may omit only the mismatched depth attachment;
// strict validation must still reject the pair and arrays never get this fallback.
func legacyDepthSize(r *report, d *webgl2.Device) {
color, err := d.CreateTexture(gpu.TextureDesc{Width: 2, Height: 2, Format: gpu.TexRGBA8})
if err != nil {
r.check("legacy-depth-color-create", false, err)
return
}
defer d.DestroyTexture(color)
depth, err := d.CreateTexture(gpu.TextureDesc{Width: 4, Height: 4, Format: gpu.TexDepth24Stencil8})
if err != nil {
r.check("legacy-depth-create", false, err)
return
}
defer d.DestroyTexture(depth)
desc := gpu.PassDesc{Target: color, DepthTarget: depth, Color: gpu.LoadClear, ClearColor: mathx.Color{G: 1, A: 1}}
r.check("strict-depth-size-validation", d.ValidateRenderTarget(desc) != nil, "mismatch accepted")
p := d.BeginFrame(32, 32).BeginPass(desc)
p.End()
d.EndFrame()
var pixel [4]byte
err = d.ReadPixels(color, 0, 0, 1, 1, pixel[:])
r.check("legacy-depth-mismatch-preserves-color-target", err == nil && pixel == [4]byte{0, 255, 0, 255}, fmt.Sprintf("%v %v", pixel, err))
}
func maskArray(r *report, d *webgl2.Device) {
h, err := d.CreateTexture(gpu.TextureDesc{Width: 3, Height: 3, Dimension: gpu.Texture2DArray, Layers: 2, Format: gpu.TexR8, Data: make([]byte, 18)})
r.check("r8-mask-array-create", err == nil, err)
if err != nil {
return
}
defer d.DestroyTexture(h)
p := d.BeginFrame(32, 32).BeginPass(gpu.PassDesc{Target: h, TargetSubresource: gpu.TextureSubresource{Layer: 1}, Color: gpu.LoadClear, ClearColor: mathx.Color{R: .75}})
p.End()
d.EndFrame()
var got [4]byte
err = d.ReadTexturePixels(h, gpu.TextureSubresource{Layer: 1}, 0, 0, 1, 1, got[:])
r.check("r8-mask-array-render", err == nil && got[0] >= 190 && got[0] <= 192 && got[1] == 0 && got[2] == 0 && got[3] == 255, fmt.Sprintf("%v %v", got, err))
err = d.ReadTexturePixels(h, gpu.TextureSubresource{}, 0, 0, 1, 1, got[:])
r.check("r8-mask-array-layer-isolation", err == nil && got == [4]byte{0, 0, 0, 255}, fmt.Sprintf("%v %v", got, err))
}
// These two deliberate capability faults exercise fallback decisions. They do
// not represent hardware that physically lacks the float extension.
func forcedFloatFailure(r *report, missingExtension bool) {
label := "forced-probe-failure"
if missingExtension {
label = "forced-no-float-extension"
}
canvas := js.Global().Get("document").Call("createElement", "canvas")
canvas.Set("width", 4)
canvas.Set("height", 4)
ctx := canvas.Call("getContext", "webgl2")
if !ctx.Truthy() {
r.check(label+"-context", false, "unavailable")
return
}
inProbe := true
var hook js.Func
method := "checkFramebufferStatus"
if missingExtension {
method = "getExtension"
}
original := ctx.Get(method).Call("bind", ctx)
hook = js.FuncOf(func(_ js.Value, args []js.Value) any {
if missingExtension && args[0].String() == "EXT_color_buffer_float" {
return js.Null()
}
if !missingExtension && inProbe {
return ctx.Get("FRAMEBUFFER_UNSUPPORTED")
}
if len(args) > 0 {
return original.Invoke(args[0])
}
return original.Invoke()
})
ctx.Set(method, hook)
defer hook.Release()
d, err := webgl2.New(surface{ctx}, webgl2.Options{Debug: true})
inProbe = false
r.check(label+"-device", err == nil, err)
if err != nil {
return
}
defer d.Close()
r.check(label+"-capability-disabled", !d.Caps().RGBA16FRenderable, d.Caps().RGBA16FRenderable)
h, err := d.CreateTexture(gpu.TextureDesc{Width: 1, Height: 1, Format: gpu.TexRGBA16F})
if err == nil {
r.check(label+"-float-target-rejected", d.ValidateRenderTarget(gpu.PassDesc{Target: h}) != nil, "HDR target accepted")
d.DestroyTexture(h)
} else {
r.check(label+"-float-allocation", false, err)
}
ldr, err := d.CreateTexture(gpu.TextureDesc{Width: 1, Height: 1, Format: gpu.TexRGBA8})
r.check(label+"-ldr-create", err == nil, err)
if err != nil {
return
}
defer d.DestroyTexture(ldr)
p := d.BeginFrame(4, 4).BeginPass(gpu.PassDesc{Target: ldr, Color: gpu.LoadClear, ClearColor: mathx.Color{G: 1, A: 1}})
p.End()
d.EndFrame()
var px [4]byte
err = d.ReadPixels(ldr, 0, 0, 1, 1, px[:])
r.check(label+"-ldr-render", err == nil && px == [4]byte{0, 255, 0, 255}, fmt.Sprintf("%v %v", px, err))
}
func floatTarget(r *report, d *webgl2.Device, label string) {
if !d.Caps().RGBA16FRenderable {
if r.FloatExtension {
r.check(label+"-rgba16f-probe", false, "EXT_color_buffer_float is present but actual draw/blend/readback probe failed")
} else {
r.Unverified = append(r.Unverified, label+"-rgba16f (EXT_color_buffer_float unavailable)")
}
return
}
h, err := d.CreateTexture(gpu.TextureDesc{Width: 2, Height: 2, Format: gpu.TexRGBA16F})
r.check(label+"-float-create", err == nil, err)
if err != nil {
return
}
defer d.DestroyTexture(h)
err = d.ValidateRenderTarget(gpu.PassDesc{Target: h})
r.check(label+"-float-framebuffer", err == nil, err)
if err != nil {
return
}
p := d.BeginFrame(32, 32).BeginPass(gpu.PassDesc{Target: h, Color: gpu.LoadClear, ClearColor: mathx.Color{R: 3, G: .5, B: .25, A: .75}})
p.End()
d.EndFrame()
var got [4]float32
err = d.ReadTexturePixelsFloat(h, gpu.TextureSubresource{}, 0, 0, 1, 1, got[:])
valid := err == nil
for i, want := range [4]float32{3, .5, .25, .75} {
valid = valid && !math.IsNaN(float64(got[i])) && math.Abs(float64(got[i]-want)) < .001
}
r.check(label+"-float-values-over-one", valid, fmt.Sprintf("%v %v", got, err))
r.check(label+"-float-reject-byte-readback", d.ReadPixels(h, 0, 0, 1, 1, make([]byte, 4)) != nil, "UNSIGNED_BYTE used for float attachment")
}
func additive(r *report, d *webgl2.Device) {
if !d.Caps().RGBA16FRenderable {
r.Unverified = append(r.Unverified, "additive HDR blend: RGBA16F unavailable")
return
}
target, err := d.CreateTexture(gpu.TextureDesc{Width: 1, Height: 1, Format: gpu.TexRGBA16F})
r.check("additive-hdr-create", err == nil, err)
if err != nil {
return
}
defer d.DestroyTexture(target)
desc := source()
desc.Label = "additive coverage"
desc.Sources[1].Source = `#version 300 es
precision highp float;
out vec4 color;
void main(){color=vec4(2.0,0.5,0.25,0.5);}`
sh, err := d.CreateShader(desc)
r.check("additive-shader-link", err == nil, err)
if err != nil {
return
}
defer d.DestroyShader(sh)
for _, test := range []struct {
name string
blend gpu.BlendState
alpha float32
}{{"additive-preserves-coverage", gpu.BlendAdditive(), .25}, {"additive-legacy-alpha-sum", gpu.BlendAdditiveLegacy(), .75}} {
pipe, err := d.CreatePipeline(gpu.PipelineDesc{Shader: sh, Primitive: gpu.PrimTriangles, Blend: test.blend})
if err != nil {
r.check(test.name, false, err)
continue
}
p := d.BeginFrame(32, 32).BeginPass(gpu.PassDesc{Target: target, Color: gpu.LoadClear, ClearColor: mathx.Color{R: 1, A: .25}})
p.SetPipeline(pipe)
p.Draw(0, 3)
p.End()
d.EndFrame()
var got [4]float32
err = d.ReadTexturePixelsFloat(target, gpu.TextureSubresource{}, 0, 0, 1, 1, got[:])
valid := err == nil
for i, want := range [4]float32{3, .5, .25, test.alpha} {
valid = valid && !math.IsNaN(float64(got[i])) && math.Abs(float64(got[i]-want)) < .001
}
r.check(test.name, valid, fmt.Sprintf("%v %v", got, err))
d.DestroyPipeline(pipe)
}
}
func restore(r *report, d *webgl2.Device, canvas js.Value) {
// Use the production platform listeners on the very canvas whose context
// is lost and restored below. Each native edge must reset exactly once.
clock, err := web.New(web.Options{Canvas: canvas})
if err != nil {
r.check("restore-platform", false, err)
return
}
defer clock.Shutdown()
if _, err = clock.CreateWindow(platform.WindowDesc{Width: 32, Height: 32}); err != nil {
r.check("restore-platform", false, err)
return
}
data := make([]byte, 8*4*2*4)
for i := 0; i < len(data); i += 4 {
data[i+1] = 255
data[i+3] = 255
}
array, err := d.CreateTexture(gpu.TextureDesc{Width: 8, Height: 4, Dimension: gpu.Texture2DArray, Layers: 2, Mips: true, Format: gpu.TexRGBA8, Data: data})
r.check("restore-array-create", err == nil, err)
if err != nil {
return
}
defer d.DestroyTexture(array)
err = d.UpdateTextureRegion(array, gpu.TextureUpdate{Subresource: gpu.TextureSubresource{Layer: 1, Mip: 1}, X: 1, Y: 0, Width: 1, Height: 1, Data: []byte{255, 255, 0, 255}})
r.check("restore-mip-update", err == nil, err)
target, err := d.CreateTexture(gpu.TextureDesc{Width: 1, Height: 1, Format: gpu.TexRGBA8})
r.check("restore-target-create", err == nil, err)
if err != nil {
return
}
defer d.DestroyTexture(target)
sh, err := d.CreateShader(source())
r.check("array-shader-link", err == nil, err)
if err != nil {
return
}
defer d.DestroyShader(sh)
pipe, err := d.CreatePipeline(gpu.PipelineDesc{Shader: sh, Primitive: gpu.PrimTriangles})
r.check("array-pipeline", err == nil, err)
if err != nil {
return
}
defer d.DestroyPipeline(pipe)
draw := func(label string) {
f := d.BeginFrame(32, 32)
p := f.BeginPass(gpu.PassDesc{Target: target, Color: gpu.LoadClear})
p.SetPipeline(pipe)
p.SetTexture(3, array, gpu.SamplerHandle{})
p.Draw(0, 3)
p.End()
invalid := f.BeginPass(gpu.PassDesc{Target: target, TargetSubresource: gpu.TextureSubresource{Layer: 1}, Color: gpu.LoadClear, ClearColor: mathx.Color{R: 1, A: 1}})
invalid.SetPipeline(pipe)
invalid.Draw(0, 3)
invalid.End()
d.EndFrame()
var got [4]byte
err := d.ReadPixels(target, 0, 0, 1, 1, got[:])
r.check(label+"-array-sampling", err == nil && got == [4]byte{0, 255, 0, 255}, fmt.Sprintf("%v %v", got, err))
r.check(label+"-invalid-pass-discards-draw", f.Stats().DrawCalls == 1, f.Stats())
}
draw("before-loss")
p := d.BeginFrame(32, 32).BeginPass(gpu.PassDesc{Target: target})
p.SetTexture(3, array, gpu.SamplerHandle{})
p.SetTexture(3, target, gpu.SamplerHandle{})
p.SetTexture(3, gpu.TextureHandle{}, gpu.SamplerHandle{})
p.End()
d.EndFrame()
ctx := canvas.Call("getContext", "webgl2")
ctx.Call("activeTexture", ctx.Get("TEXTURE0").Int()+3)
r.check("zero-texture-clears-all-targets", !ctx.Call("getParameter", ctx.Get("TEXTURE_BINDING_2D_ARRAY")).Truthy() && !ctx.Call("getParameter", ctx.Get("TEXTURE_BINDING_2D")).Truthy(), "stale 2D/array binding")
// Real compile/link submission, deliberately left unconsumed until after
// loss. The driver may finish before loss; publication is still pending.
job, err := d.BeginShaderCompile(source())
r.check("pending-compile-submitted", err == nil, err)
if err != nil {
return
}
defer job.Cancel()
lostEvents, restoredEvents := 0, 0
onLost := js.FuncOf(func(js.Value, []js.Value) any { lostEvents++; return nil })
onRestore := js.FuncOf(func(js.Value, []js.Value) any { restoredEvents++; return nil })
canvas.Call("addEventListener", "webglcontextlost", onLost)
canvas.Call("addEventListener", "webglcontextrestored", onRestore)
defer func() {
canvas.Call("removeEventListener", "webglcontextlost", onLost)
canvas.Call("removeEventListener", "webglcontextrestored", onRestore)
onLost.Release()
onRestore.Release()
}()
before := d.ContextGeneration()
if !d.SimulateLoss(true) {
r.check("WEBGL_lose_context", false, "extension unavailable")
return
}
if !waitFor(func() bool { return d.Lost() && lostEvents == 1 }) {
r.check("loss-event", false, "timed out")
return
}
r.check("loss-event", true, nil)
r.check("loss-clock-reset-once", clock.ConsumeFrameClockReset() && !clock.ConsumeFrameClockReset(), "lost event must rebase only the first ordinary frame")
var px [4]byte
r.check("lost-readback-rejected", d.ReadPixels(target, 0, 0, 1, 1, px[:]) != nil, "readback accepted on lost context")
_, done, pollErr := job.Poll()
r.check("old-generation-candidate-cancelled", done && pollErr != nil, fmt.Sprintf("done=%v err=%v", done, pollErr))
err = d.UpdateTextureRegion(array, gpu.TextureUpdate{Subresource: gpu.TextureSubresource{Layer: 1, Mip: 2}, X: 1, Y: 0, Width: 1, Height: 1, Data: []byte{255, 0, 0, 255}})
r.check("lost-context-keeps-cpu-mip-update", err == nil, err)
d.SimulateLoss(false)
if !waitFor(func() bool { return restoredEvents == 1 && d.Restores() == 1 && !d.Lost() }) {
r.check("restore-event", false, "timed out")
return
}
r.check("restore-event", d.ContextGeneration() > before, fmt.Sprintf("generation %d -> %d", before, d.ContextGeneration()))
r.check("restore-clock-reset-once", clock.ConsumeFrameClockReset() && !clock.ConsumeFrameClockReset(), "restored event must rebase only the first ordinary frame")
r.Restores = d.Restores()
draw("after-loss")
var got [8]byte
err = d.ReadTexturePixels(array, gpu.TextureSubresource{Layer: 1, Mip: 1}, 0, 0, 2, 1, got[:])
r.check("restore-manual-mip-and-generated-neighbour", err == nil && got == [8]byte{0, 255, 0, 255, 255, 255, 0, 255}, fmt.Sprintf("%v %v", got, err))
err = d.ReadTexturePixels(array, gpu.TextureSubresource{Layer: 1, Mip: 2}, 0, 0, 2, 1, got[:])
r.check("restore-update-made-during-loss", err == nil && got == [8]byte{0, 255, 0, 255, 255, 0, 0, 255}, fmt.Sprintf("%v %v", got, err))
floatTarget(r, d, "restored")
fresh, err := d.BeginShaderCompile(source())
r.check("restored-compile-submitted", err == nil, err)
if err == nil {
defer fresh.Cancel()
var shader gpu.ShaderHandle
var complete bool
var compileErr error
ok := waitFor(func() bool { shader, complete, compileErr = fresh.Poll(); return complete })
r.check("restored-compile-published", ok && compileErr == nil && !shader.IsZero(), compileErr)
if !shader.IsZero() {
d.DestroyShader(shader)
}
}
}
func main() {
r := &report{}
defer func() {
if p := recover(); p != nil {
r.Failures = append(r.Failures, fmt.Sprint(p))
}
data, _ := json.Marshal(r)
js.Global().Set("aegoGPUResults", string(data))
}()
canvas := js.Global().Get("document").Call("getElementById", "surface")
ctx := canvas.Call("getContext", "webgl2", map[string]any{"alpha": true, "premultipliedAlpha": true, "antialias": false, "preserveDrawingBuffer": true})
if !ctx.Truthy() {
r.check("WebGL2-context", false, "unavailable")
return
}
r.FloatExtension = ctx.Call("getExtension", "EXT_color_buffer_float").Truthy()
r.Parallel = ctx.Call("getExtension", "KHR_parallel_shader_compile").Truthy()
if !r.Parallel {
r.Unverified = append(r.Unverified, "real KHR_parallel_shader_compile branch (extension unavailable)")
}
d, err := webgl2.New(surface{ctx}, webgl2.Options{Debug: true})
r.check("device-create", err == nil, err)
if err != nil {
return
}
defer d.Close()
c := d.Caps()
r.Renderer = c.Renderer
r.FloatRenderable = c.RGBA16FRenderable
r.MaxFragmentTextureUnits = c.MaxFragmentTextureUnits
r.MaxArrayTextureLayers = c.MaxArrayTextureLayers
r.check("fragment-unit-capability", c.MaxFragmentTextureUnits == ctx.Call("getParameter", ctx.Get("MAX_TEXTURE_IMAGE_UNITS")).Int(), c.MaxFragmentTextureUnits)
r.check("array-layer-capability", c.MaxArrayTextureLayers == ctx.Call("getParameter", ctx.Get("MAX_ARRAY_TEXTURE_LAYERS")).Int(), c.MaxArrayTextureLayers)
arrayLayers(r, d)
legacyDepthSize(r, d)
maskArray(r, d)
floatTarget(r, d, "initial")
additive(r, d)
restore(r, d, canvas)
r.check("released-resource-counts", d.ResourceStats() == (gpu.ResourceStats{}), d.ResourceStats())
forcedFloatFailure(r, true)
forcedFloatFailure(r, false)
}