├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── atlas.go
├── attr.go
├── batch.go
├── blend.go
├── buffer.go
├── color.go
├── debugui
├── imgui_desktop.go
├── imgui_wasm.go
├── main.frag
├── main.vert
└── renderer_desktop.go
├── defaults.go
├── examples
├── 3d
│ └── main.go
├── Makefile
├── assets
│ ├── assets.go
│ ├── atlas-msdf.json
│ ├── atlas-msdf.png
│ ├── atlas.json
│ ├── atlas.png
│ ├── button.png
│ ├── button_hover.png
│ ├── button_press.png
│ ├── gopher-small.png
│ ├── gopher.png
│ ├── panel.png
│ └── panel_inner.png
├── controller
│ └── main.go
├── frame
│ └── main.go
├── gophermark
│ ├── Makefile
│ ├── main.go
│ └── slicegen_test.go
├── graph
│ └── main.go
├── idea.go
├── imgui
│ └── main.go
├── pixelart
│ └── main.go
├── sdf
│ └── main.go
├── simple
│ └── main.go
├── ui
│ └── main.go
└── web
│ ├── Makefile
│ ├── assets
│ ├── css
│ │ └── main.css
│ └── scripts
│ │ └── main.js
│ ├── index.html
│ ├── main.go
│ └── wasm_exec.js
├── frame.go
├── gamepad.go
├── geometry.go
├── glitch.go
├── go.mod
├── go.sum
├── graph
└── graph.go
├── internal
├── gl
│ ├── README.md
│ ├── consts.go
│ ├── doc.go
│ ├── gl_opengl.go
│ ├── gl_opengles.go
│ ├── gl_webgl_wasm.go
│ ├── types.go
│ ├── types_opengl.go
│ ├── types_opengles.go
│ └── types_webgl_wasm.go
├── glfw
│ ├── .gitignore
│ ├── README.md
│ ├── browser_wasm.go
│ ├── context_webgl_wasm.go
│ ├── desktop.go
│ ├── glfw.go
│ ├── hint_glfw.go
│ └── hint_js.go
└── mainthread
│ ├── mainthread.go
│ └── mainthread_js.go
├── key.go
├── math.go
├── mesh.go
├── model.go
├── opengl.go
├── pass.go
├── shader.go
├── shaders
├── diffuse.fs
├── flat.fs
├── mesh.vs
├── minimap.fs
├── msdf.fs
├── pixel.fs
├── pixel.vs
├── sdf.fs
├── shaders.go
├── sprite-repeat.fs
├── sprite-water.fs
├── sprite.fs
├── sprite.vs
├── sprite_100.fs
├── sprite_100.vs
└── subPixel.fs
├── sorter.go
├── sprite.go
├── state.go
├── text.go
├── texture.go
├── ui
├── group.go
├── hash.go
├── layout.go
├── style.go
├── ui.go
└── widget.go
└── window.go
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 | on:
3 | push:
4 | branches: [ master ]
5 | pull_request:
6 | branches: [ master ]
7 |
8 | jobs:
9 | build:
10 | strategy:
11 | matrix:
12 | go-version: ['1.21']
13 | os: [ubuntu-latest, macos-latest, windows-latest]
14 | runs-on: ${{ matrix.os }}
15 | steps:
16 |
17 | - name: Update apt
18 | if: runner.os == 'Linux'
19 | run: sudo apt-get update
20 | - name: Install dependencies
21 | if: runner.os == 'Linux'
22 | run: sudo apt-get install libgl1-mesa-dev xorg-dev libasound2-dev gcc-mingw-w64 g++-mingw-w64
23 |
24 | - uses: actions/setup-go@v3
25 | with:
26 | go-version: ${{ matrix.go-version }}
27 | - uses: actions/checkout@v3
28 |
29 | - name: make All
30 | run: make all
31 |
32 | # Maybe one day
33 | # - name: Upload coverage to Codecov
34 | # uses: codecov/codecov-action@v2
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.wasm
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 unitoftime
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: test
2 |
3 | test:
4 | go fmt ./...
5 | go test ./...
6 |
7 | upgrade:
8 | go get -u ./...
9 | go mod tidy
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://pkg.go.dev/github.com/unitoftime/glitch)
2 | [](https://github.com/unitoftime/glitch/actions/workflows/build.yml)
3 |
4 |
5 | `Warning: This library is very much a work in progress. But you're welcome to check it out and provide feedback/bugs if you want.`
6 |
7 | ## Overview
8 | Glitch is a shader based rendering library built on top of OpenGL and WebGL. At a high level, I'd like glitch to be data driven. Shaders are just programs that run on the GPU, so my objective is to make Glitch a platform that makes it easier to do the things that are hard in rendering:
9 | 1. Efficiently ordering, moving, and batching data to the GPU
10 | 2. Efficiently executing programs on that copied data
11 |
12 | ## Platform Support
13 | Currently, we compile to:
14 | * Desktop (Windows, Linux)
15 | * Browser (via WebAssembly)
16 |
17 | Platforms that I'd like to add, but haven't added or haven't tested:
18 | * Desktop (MacOS - OpenGL is deprecated, I also don't own a mac. So it's hard to test)
19 | * Mobile Apps
20 | * Mobile Browsers
21 |
22 | ## Usage
23 | You can look at the examples folder, sometimes they go out of date, but I try to keep them working. Because APIs are shifting I don't have definite APIs defined yet.
24 |
--------------------------------------------------------------------------------
/atlas.go:
--------------------------------------------------------------------------------
1 | package glitch
2 |
3 | import (
4 | "image"
5 |
6 | "github.com/unitoftime/flow/glm"
7 | )
8 |
9 | // RBG: msdf-atlas-gen -type mtsdf -emrange 0.2 -dimensions 255 255 -font ~/Library/Fonts/FiraSans-Regular.otf -imageout assets/FiraSans-Regular.png -json assets/FiraSans-Regular.json
10 |
11 | // ./msdf-atlas-gen/build/bin/msdf-atlas-gen -font ./Lato-Black.ttf -imageout atlas.png -json atlas.json -pots -size 32 -yorigin top -emrange 0.2 -type mtsdf
12 |
13 | // MSDF
14 | // ../msdf-atlas-gen/build/bin/msdf-atlas-gen -font ./Lato-Black.ttf -imageout atlas.png -json atlas.json -pots -size 32 -yorigin top -pxrange 10
15 |
16 | // SDF
17 | // ./msdf-atlas-gen/build/bin/msdf-atlas-gen -font ./Lato-Black.ttf -imageout atlas.png -json atlas.json -pots -size 32 -yorigin top -pxrange 10
18 | // PlaneBounds: https://github.com/Chlumsky/msdf-atlas-gen/issues/2
19 | type SdfAtlasPreamble struct {
20 | Type string
21 | DistanceRange float64
22 | DistanceRangeMiddle int
23 | Size int
24 | Width int
25 | Height int
26 | YOrigin string
27 | }
28 |
29 | type SdfMetrics struct {
30 | EmSize int
31 | LineHeight float64
32 | Ascender float64
33 | Descender float64
34 | UnderlineY float64
35 | UnderlineThickness float64
36 | }
37 |
38 | type AtlasRect struct {
39 | Left, Bottom, Right, Top float64
40 | }
41 |
42 | type GlyphData struct {
43 | Unicode int
44 | Advance float64
45 | PlaneBounds AtlasRect
46 | AtlasBounds AtlasRect
47 | }
48 |
49 | type SdfAtlas struct {
50 | Atlas SdfAtlasPreamble
51 | Metrics SdfMetrics
52 | Glyphs []GlyphData
53 | }
54 |
55 | // Multiply by font size to get in pixels, then divide by texture size
56 | func sdfUnitToFloat(sdfUnit float64, fontSize, texSize int) float64 {
57 | return (sdfUnit * float64(fontSize)) / float64(texSize)
58 | }
59 |
60 | func AtlasFromSdf(sdf SdfAtlas, sdfImg image.Image, kerning float64) (*Atlas, error) {
61 | texture := NewTexture(sdfImg, true) // TODO: Smoothing for sdf?
62 |
63 | // height := sdfUnitToFloat(sdf.Metrics.LineHeight, sdf.Atlas.Size, sdf.Atlas.Width)
64 | height := sdf.Metrics.LineHeight * float64(sdf.Atlas.Size)
65 | ascent := sdf.Metrics.Ascender * float64(sdf.Atlas.Size)
66 | descent := sdf.Metrics.Descender * float64(sdf.Atlas.Size)
67 | // height := sdfUnitToFloat(sdf.Metrics.LineHeight, sdf.Atlas.Size, sdf.Atlas.Width)
68 | // ascent := sdfUnitToFloat(-sdf.Metrics.Ascender, sdf.Atlas.Size, sdf.Atlas.Width)
69 | // descent := sdfUnitToFloat(sdf.Metrics.Descender, sdf.Atlas.Size, sdf.Atlas.Width)
70 |
71 | atlas := &Atlas{
72 | mapping: make(map[rune]Glyph),
73 | // ascent: floatToFixed(ascent),//floatToFixed(sdf.Metrics.Ascender),
74 | // descent: floatToFixed(descent), //floatToFixed(sdf.Metrics.Descender),
75 | // height: floatToFixed(height),
76 | ascent: -ascent, //floatToFixed(sdf.Metrics.Ascender),
77 | descent: descent, //floatToFixed(sdf.Metrics.Descender),
78 | height: height,
79 | // ascent: descent,
80 | // descent: ascent,
81 | // height: height,
82 | texture: texture,
83 | // pixelPerfect: true,
84 | defaultKerning: kerning,
85 | defaultMaterial: DefaultMsdfMaterial(texture),
86 | }
87 |
88 | for _, g := range sdf.Glyphs {
89 | // pb := R(
90 | // sdfUnitToFloat(g.PlaneBounds.Left, sdf.Atlas.Size, sdf.Atlas.Width),
91 | // sdfUnitToFloat(g.PlaneBounds.Bottom, sdf.Atlas.Size, sdf.Atlas.Height),
92 | // sdfUnitToFloat(g.PlaneBounds.Right, sdf.Atlas.Size, sdf.Atlas.Width),
93 | // sdfUnitToFloat(g.PlaneBounds.Top, sdf.Atlas.Size, sdf.Atlas.Height),
94 | // )
95 | // ww := pb.W() * float64(sdf.Atlas.Width)
96 | // if ww != g.AtlasBounds.Right - g.AtlasBounds.Left {
97 | // fmt.Println("Failed:", rune(g.Unicode), ww, g.AtlasBounds.Right - g.AtlasBounds.Left)
98 | // panic("aslkfdjsalfd")
99 | // }
100 |
101 | // bearingRect := image.Rect(
102 | // int(g.PlaneBounds.Left),
103 | // )
104 | // bearingX := float64((bearingRect.Min.X * 1000000).Floor()) / (1000000 * fSize)
105 | // bearingY := float64((-bearingRect.Max.Y * 1000000).Floor()) / (1000000 * fSize)
106 | bearingX := sdfUnitToFloat(g.PlaneBounds.Left, sdf.Atlas.Size, sdf.Atlas.Width)
107 | bearingY := sdfUnitToFloat(-g.PlaneBounds.Bottom, sdf.Atlas.Size, sdf.Atlas.Height)
108 |
109 | // h := -g.PlaneBounds.Top + g.PlaneBounds.Bottom
110 | // bearingY := sdfUnitToFloat(h - g.PlaneBounds.Bottom, sdf.Atlas.Size, sdf.Atlas.Height)
111 |
112 | glyph := Glyph{
113 | // Advance: (g.Advance * float64(sdf.Atlas.Size)) / float64(sdf.Atlas.Width),
114 | Advance: sdfUnitToFloat(g.Advance, sdf.Atlas.Size, sdf.Atlas.Width),
115 | Bearing: Vec2{bearingX, bearingY},
116 | BoundsUV: glm.R(
117 | g.AtlasBounds.Left/float64(sdf.Atlas.Width),
118 | g.AtlasBounds.Top/float64(sdf.Atlas.Height),
119 | g.AtlasBounds.Right/float64(sdf.Atlas.Width),
120 | g.AtlasBounds.Bottom/float64(sdf.Atlas.Height),
121 | ).Norm(),
122 | }
123 | atlas.mapping[rune(g.Unicode)] = glyph
124 | }
125 |
126 | atlas.tmpText = atlas.Text("", 1.0)
127 |
128 | return atlas, nil
129 | }
130 |
--------------------------------------------------------------------------------
/attr.go:
--------------------------------------------------------------------------------
1 | package glitch
2 |
3 | // // TODO - right now we only support floats (for simplicity)
4 | // type VertexFormat []VertexAttr
5 | // type UniformFormat []Attr
6 |
7 | // type VertexAttr struct {
8 | // Attr // The underlying Attribute
9 | // Swizzle SwizzleType // This defines how the shader wants to map a generic object (like a mesh, to the shader buffers)
10 | // }
11 |
12 | // type Attr struct {
13 | // Name string
14 | // Type AttrType
15 | // }
16 | // func (a Attr) GetBuffer() any {
17 | // switch a.Type {
18 | // case AttrFloat:
19 | // return &[]float32{}
20 | // case AttrVec2:
21 | // return &[]glVec2{}
22 | // case AttrVec3:
23 | // return &[]glVec3{}
24 | // case AttrVec4:
25 | // return &[]glVec4{}
26 | // default:
27 | // panic(fmt.Sprintf("Attr not valid for GetBuffer: %v", a))
28 | // }
29 | // }
30 |
31 | // // Returns the size of the attribute type
32 | // func (a Attr) Size() int {
33 | // switch a.Type {
34 | // case AttrInt: return 1
35 | // case AttrFloat: return 1
36 | // case AttrVec2: return 2
37 | // case AttrVec3: return 3
38 | // case AttrVec4: return 4
39 | // case AttrMat2: return 2 * 2
40 | // case AttrMat23: return 2 * 3
41 | // case AttrMat24: return 2 * 4
42 | // case AttrMat3: return 3 * 3
43 | // case AttrMat32: return 3 * 2
44 | // case AttrMat34: return 3 * 4
45 | // case AttrMat4: return 4 * 4
46 | // case AttrMat42: return 4 * 2
47 | // case AttrMat43: return 4 * 3
48 | // default: panic(fmt.Sprintf("Invalid Attribute: %v", a))
49 | // }
50 | // }
51 |
52 | // // This type is used to define the underlying data type of a vertex attribute or uniform attribute
53 | // type AttrType uint8
54 | // const (
55 | // // TODO - others
56 | // AttrInt AttrType = iota
57 | // AttrFloat
58 | // AttrVec2
59 | // AttrVec3
60 | // AttrVec4
61 | // AttrMat2
62 | // AttrMat23
63 | // AttrMat24
64 | // AttrMat3
65 | // AttrMat32
66 | // AttrMat34
67 | // AttrMat4
68 | // AttrMat42
69 | // AttrMat43
70 | // )
71 |
72 | // // This type is used to define how generic meshes map into specific shader buffers
73 | // type SwizzleType uint8
74 | // const (
75 | // PositionXY SwizzleType = iota
76 | // PositionXYZ
77 | // NormalXY
78 | // NormalXYZ
79 | // ColorR
80 | // ColorRG
81 | // ColorRGB
82 | // ColorRGBA
83 | // TexCoordXY
84 | // // TexCoordXYZ // Is this a thing?
85 | // )
86 |
--------------------------------------------------------------------------------
/blend.go:
--------------------------------------------------------------------------------
1 | package glitch
2 |
3 | import "github.com/unitoftime/glitch/internal/gl"
4 |
5 | // Note: These are all packed into uint8s to reduce size of the Material object
6 | type BlendMode uint8
7 |
8 | const (
9 | BlendModeNone BlendMode = iota
10 | BlendModeNormal
11 | BlendModeMultiply
12 | )
13 |
14 | type blendModeData struct {
15 | src, dst gl.Enum
16 | }
17 |
18 | var blendModeLut []blendModeData = []blendModeData{
19 | // Note: This is what I used before premult: gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA
20 | BlendModeNormal: {gl.ONE, gl.ONE_MINUS_SRC_ALPHA},
21 | // BlendModeNormal: {gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA},
22 | // BlendModeNormal: {gl.SRC_ALPHA, gl.ONE},
23 | BlendModeMultiply: {gl.DST_COLOR, gl.ZERO},
24 | }
25 |
26 | type DepthMode uint8
27 |
28 | const (
29 | DepthModeNone DepthMode = iota
30 | DepthModeLess
31 | DepthModeLequal
32 | )
33 |
34 | type depthModeData struct {
35 | mode gl.Enum
36 | }
37 |
38 | var depthModeLut []depthModeData = []depthModeData{
39 | DepthModeNone: {},
40 | DepthModeLess: {gl.LESS},
41 | DepthModeLequal: {gl.LEQUAL},
42 | }
43 |
44 | type CullMode uint8
45 |
46 | const (
47 | CullModeNone CullMode = iota
48 | CullModeNormal
49 | )
50 |
51 | type cullModeData struct {
52 | face, dir gl.Enum
53 | }
54 |
55 | var cullModeLut []cullModeData = []cullModeData{
56 | CullModeNone: {},
57 | CullModeNormal: {gl.BACK, gl.CCW},
58 | }
59 |
60 | // // https://registry.khronos.org/OpenGL-Refpages/gl4/html/glBlendFunc.xhtml
61 | // type BlendMode struct {
62 | // src, dst gl.Enum
63 | // }
64 | // var BlendModeNormal = BlendMode{
65 | // // Note: This is what I used before premult: gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA
66 | // gl.ONE,
67 | // gl.ONE_MINUS_SRC_ALPHA,
68 | // }
69 |
70 | // // // TODO: Untested
71 | // // var BlendModeAdd = BlendMode{
72 | // // gl.SRC_ALPHA, gl.ONE
73 | // // }
74 |
75 | // var BlendModeMultiply = BlendMode{
76 | // gl.DST_COLOR, gl.ZERO,
77 | // }
78 |
79 | // type DepthMode struct {
80 | // mode gl.Enum
81 | // }
82 | // var ( // TODO: Can these be constants? Will that break wasm?
83 | // DepthModeNone = DepthMode{}
84 | // DepthModeLess = DepthMode{gl.LESS}
85 | // DepthModeLequal = DepthMode{gl.LEQUAL}
86 | // )
87 |
88 | // type CullMode struct {
89 | // face gl.Enum
90 | // dir gl.Enum
91 | // }
92 | // var ( // TODO: Can these be constants? Will that break wasm?
93 | // CullModeNone = CullMode{}
94 | // CullModeNormal = CullMode{gl.BACK, gl.CCW}
95 | // )
96 |
97 | // // https://registry.khronos.org/OpenGL-Refpages/gl4/html/glBlendFunc.xhtml
98 | // type BlendMode struct {
99 | // src, dst gl.Enum
100 | // }
101 | // var BlendModeNormal = BlendMode{
102 | // // Note: This is what I used before premult: gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA
103 | // gl.ONE,
104 | // gl.ONE_MINUS_SRC_ALPHA,
105 | // }
106 |
107 | // // // TODO: Untested
108 | // // var BlendModeAdd = BlendMode{
109 | // // gl.SRC_ALPHA, gl.ONE
110 | // // }
111 |
112 | // var BlendModeMultiply = BlendMode{
113 | // gl.DST_COLOR, gl.ZERO,
114 | // }
115 |
116 | // type DepthMode struct {
117 | // mode gl.Enum
118 | // }
119 | // var ( // TODO: Can these be constants? Will that break wasm?
120 | // DepthModeNone = DepthMode{}
121 | // DepthModeLess = DepthMode{gl.LESS}
122 | // DepthModeLequal = DepthMode{gl.LEQUAL}
123 | // )
124 |
125 | // type CullMode struct {
126 | // face gl.Enum
127 | // dir gl.Enum
128 | // }
129 | // var ( // TODO: Can these be constants? Will that break wasm?
130 | // CullModeNone = CullMode{}
131 | // CullModeNormal = CullMode{gl.BACK, gl.CCW}
132 | // )
133 |
--------------------------------------------------------------------------------
/buffer.go:
--------------------------------------------------------------------------------
1 | package glitch
2 |
3 | // type Buffer struct {
4 | // buf *VertexBuffer
5 | // }
6 |
7 | // func NewBuffer() *Buffer {
8 | // return &Buffer{}
9 | // }
10 |
11 | // func (b *Buffer) Add(mesh *Mesh, matrix Mat4, mask RGBA, material Material, translucent bool) {
12 | // if b.material == nil {
13 | // b.material = material
14 | // } else {
15 | // if b.material != material {
16 | // panic("Materials must match inside a batch!")
17 | // }
18 | // }
19 |
20 | // b.mesh.generation++
21 |
22 | // // If anything translucent is added to the batch, then we will consider the entire thing translucent
23 | // b.Translucent = b.Translucent || translucent
24 |
25 | // mat := matrix.gl()
26 |
27 | // // Append each index
28 | // currentElement := uint32(len(b.mesh.positions))
29 | // for i := range mesh.indices {
30 | // b.mesh.indices = append(b.mesh.indices, currentElement + mesh.indices[i])
31 | // }
32 |
33 | // // Append each position
34 | // for i := range mesh.positions {
35 | // b.mesh.positions = append(b.mesh.positions, mat.Apply(mesh.positions[i]))
36 | // }
37 |
38 | // // Calculate the bounding box of the mesh we just merged in
39 | // // Because we already figured out the first index of the new mesh (ie `currentElement`) we can just slice off the end of the new mesh
40 | // posBuf := b.mesh.positions[int(currentElement):]
41 | // min := posBuf[0]
42 | // max := posBuf[0]
43 | // for i := range posBuf {
44 | // // X
45 | // if posBuf[i][0] < min[0] {
46 | // min[0] = posBuf[i][0]
47 | // }
48 | // if posBuf[i][0] > max[0] {
49 | // max[0] = posBuf[i][0]
50 | // }
51 |
52 | // // Y
53 | // if posBuf[i][1] < min[1] {
54 | // min[1] = posBuf[i][1]
55 | // }
56 | // if posBuf[i][1] > max[1] {
57 | // max[1] = posBuf[i][1]
58 | // }
59 |
60 | // // Z
61 | // if posBuf[i][2] < min[2] {
62 | // min[2] = posBuf[i][2]
63 | // }
64 | // if posBuf[i][2] > max[2] {
65 | // max[2] = posBuf[i][2]
66 | // }
67 | // }
68 |
69 | // newBounds := Box{
70 | // Min: Vec3{float64(min[0]), float64(min[1]), float64(min[2])},
71 | // Max: Vec3{float64(max[0]), float64(max[1]), float64(max[2])},
72 | // }
73 | // b.mesh.bounds = b.mesh.bounds.Union(newBounds)
74 |
75 | // renormalizeMat := matrix.Inv().Transpose().gl()
76 | // for i := range mesh.normals {
77 | // b.mesh.normals = append(b.mesh.normals, renormalizeMat.Apply(mesh.normals[i]))
78 | // }
79 |
80 | // for i := range mesh.colors {
81 | // // TODO - vec4 mult function
82 | // b.mesh.colors = append(b.mesh.colors, glVec4{
83 | // mesh.colors[i][0] * float32(mask.R),
84 | // mesh.colors[i][1] * float32(mask.G),
85 | // mesh.colors[i][2] * float32(mask.B),
86 | // mesh.colors[i][3] * float32(mask.A),
87 | // })
88 | // }
89 |
90 | // // TODO - is a copy faster?
91 | // for i := range mesh.texCoords {
92 | // b.mesh.texCoords = append(b.mesh.texCoords, mesh.texCoords[i])
93 | // }
94 |
95 | // // if b.material == nil {
96 | // // b.material = material
97 | // // } else {
98 | // // if b.material != material {
99 | // // panic("Materials must match inside a batch!")
100 | // // }
101 | // // }
102 |
103 | // // mat := matrix.gl()
104 |
105 | // // posBuf := make([]glVec3, len(mesh.positions))
106 | // // for i := range mesh.positions {
107 | // // posBuf[i] = mat.Apply(mesh.positions[i])
108 | // // }
109 |
110 | // // min := posBuf[0]
111 | // // max := posBuf[0]
112 | // // for i := range posBuf {
113 | // // // X
114 | // // if posBuf[i][0] < min[0] {
115 | // // min[0] = posBuf[i][0]
116 | // // }
117 | // // if posBuf[i][0] > max[0] {
118 | // // max[0] = posBuf[i][0]
119 | // // }
120 |
121 | // // // Y
122 | // // if posBuf[i][1] < min[1] {
123 | // // min[1] = posBuf[i][1]
124 | // // }
125 | // // if posBuf[i][1] > max[1] {
126 | // // max[1] = posBuf[i][1]
127 | // // }
128 |
129 | // // // Z
130 | // // if posBuf[i][2] < min[2] {
131 | // // min[2] = posBuf[i][2]
132 | // // }
133 | // // if posBuf[i][2] > max[2] {
134 | // // max[2] = posBuf[i][2]
135 | // // }
136 | // // }
137 |
138 | // // newBounds := Box{
139 | // // Min: Vec3{float64(min[0]), float64(min[1]), float64(min[2])},
140 | // // Max: Vec3{float64(max[0]), float64(max[1]), float64(max[2])},
141 | // // }
142 |
143 | // // renormalizeMat := matrix.Inv().Transpose().gl()
144 | // // normBuf := make([]glVec3, len(mesh.normals))
145 | // // for i := range mesh.normals {
146 | // // normBuf[i] = renormalizeMat.Apply(mesh.normals[i])
147 | // // }
148 |
149 | // // colBuf := make([]glVec4, len(mesh.colors))
150 | // // for i := range mesh.colors {
151 | // // // TODO - vec4 mult function
152 | // // colBuf[i] = glVec4{
153 | // // mesh.colors[i][0] * float32(mask.R),
154 | // // mesh.colors[i][1] * float32(mask.G),
155 | // // mesh.colors[i][2] * float32(mask.B),
156 | // // mesh.colors[i][3] * float32(mask.A),
157 | // // }
158 | // // }
159 |
160 | // // // TODO - is a copy faster?
161 | // // texBuf := make([]glVec2, len(mesh.texCoords))
162 | // // for i := range mesh.texCoords {
163 | // // texBuf[i] = mesh.texCoords[i]
164 | // // }
165 |
166 | // // indices := make([]uint32, len(mesh.indices))
167 | // // for i := range mesh.indices {
168 | // // indices[i] = mesh.indices[i]
169 | // // }
170 |
171 | // // m2 := &Mesh{
172 | // // positions: posBuf,
173 | // // normals: normBuf,
174 | // // colors: colBuf,
175 | // // texCoords: texBuf,
176 | // // indices: indices,
177 | // // bounds: newBounds,
178 | // // }
179 |
180 | // // b.mesh.Append(m2)
181 | // }
182 |
183 | // func (b *Buffer) Clear() {
184 | // b.mesh.Clear()
185 | // b.material = nil
186 | // b.Translucent = false
187 | // }
188 |
189 | // func (b *Buffer) Draw(target BatchTarget, matrix Mat4) {
190 | // target.Add(b.mesh, matrix, RGBA{1.0, 1.0, 1.0, 1.0}, b.material, b.Translucent)
191 | // }
192 |
193 | // func (b *Buffer) DrawColorMask(target BatchTarget, matrix Mat4, color RGBA) {
194 | // target.Add(b.mesh, matrix, color, b.material, b.Translucent)
195 | // }
196 |
197 | // func (b *Buffer) RectDraw(target BatchTarget, bounds Rect) {
198 | // b.RectDrawColorMask(target, bounds, RGBA{1, 1, 1, 1})
199 | // }
200 | // // TODO: Generalize this rectdraw logic. Copy paseted from Sprite
201 | // func (b *Buffer) RectDrawColorMask(target BatchTarget, bounds Rect, mask RGBA) {
202 | // // pass.SetTexture(0, s.texture)
203 | // // pass.Add(s.mesh, matrix, RGBA{1.0, 1.0, 1.0, 1.0}, s.material)
204 |
205 | // batchBounds := b.Bounds().Rect()
206 | // matrix := Mat4Ident
207 | // matrix.Scale(bounds.W() / batchBounds.W(), bounds.H() / batchBounds.H(), 1).Translate(bounds.W()/2 + bounds.Min[0], bounds.H()/2 + bounds.Min[1], 0)
208 | // target.Add(b.mesh, matrix, mask, b.material, false)
209 | // }
210 |
211 | // func (b *Buffer) Bounds() Box {
212 | // return b.mesh.Bounds()
213 | // }
214 |
--------------------------------------------------------------------------------
/color.go:
--------------------------------------------------------------------------------
1 | package glitch
2 |
3 | import "github.com/unitoftime/flow/glm"
4 |
5 | var (
6 | White = glm.White
7 | Black = glm.Black
8 | )
9 |
10 | // var (
11 | // White = RGBA{1, 1, 1, 1}
12 | // Black = RGBA{0, 0, 0, 1}
13 | // )
14 |
15 | // // Premultipled RGBA value scaled from [0, 1.0]
16 | // type RGBA struct {
17 | // R,G,B,A float64
18 | // }
19 |
20 | // // TODO - conversion from golang colors
21 | // func FromUint8(r, g, b, a uint8) RGBA {
22 | // return RGBA{
23 | // float64(r) / float64(math.MaxUint8),
24 | // float64(g) / float64(math.MaxUint8),
25 | // float64(b) / float64(math.MaxUint8),
26 | // float64(a) / float64(math.MaxUint8),
27 | // }
28 | // }
29 |
30 | // func Alpha(a float64) RGBA {
31 | // return RGBA{a, a, a, a}
32 | // }
33 |
34 | // func Greyscale(g float64) RGBA {
35 | // return RGBA{g, g, g, 1.0}
36 | // }
37 |
38 | // func FromStraightRGBA(r, g, b float64, a float64) RGBA {
39 | // return RGBA{r * a, g * a, b * a, a}
40 | // }
41 |
42 | // func FromNRGBA(c color.NRGBA) RGBA {
43 | // r, g, b, a := c.RGBA()
44 |
45 | // return RGBA{
46 | // float64(r) / float64(math.MaxUint16),
47 | // float64(g) / float64(math.MaxUint16),
48 | // float64(b) / float64(math.MaxUint16),
49 | // float64(a) / float64(math.MaxUint16),
50 | // }
51 | // }
52 |
53 | // func FromRGBA(c color.RGBA) RGBA {
54 | // return FromUint8(c.R, c.G, c.B, c.A)
55 | // }
56 |
57 | // func FromColor(c color.Color) RGBA {
58 | // r, g, b, a := c.RGBA()
59 |
60 | // return RGBA{
61 | // float64(r) / float64(math.MaxUint16),
62 | // float64(g) / float64(math.MaxUint16),
63 | // float64(b) / float64(math.MaxUint16),
64 | // float64(a) / float64(math.MaxUint16),
65 | // }
66 | // }
67 | // func (c1 RGBA) Mult(c2 RGBA) RGBA {
68 | // return RGBA{
69 | // c1.R * c2.R,
70 | // c1.G * c2.G,
71 | // c1.B * c2.B,
72 | // c1.A * c2.A,
73 | // }
74 | // }
75 |
76 | // func (c1 RGBA) Add(c2 RGBA) RGBA {
77 | // return RGBA{
78 | // c1.R + c2.R,
79 | // c1.G + c2.G,
80 | // c1.B + c2.B,
81 | // c1.A + c2.A,
82 | // }
83 | // }
84 |
85 | // func (c RGBA) Desaturate(val float64) RGBA {
86 | // // https://stackoverflow.com/questions/70966873/algorithm-to-desaturate-rgb-color
87 | // i := (c.R + c.G + c.B) / 3
88 |
89 | // dr := i - c.R
90 | // dg := i - c.G
91 | // db := i - c.B
92 |
93 | // return RGBA{
94 | // c.R + (dr * val),
95 | // c.G + (dg * val),
96 | // c.B + (db * val),
97 | // c.A,
98 | // }
99 | // }
100 |
101 | // func (c RGBA) gl() glVec4 {
102 | // return glVec4{float32(c.R), float32(c.G), float32(c.B), float32(c.A)}
103 | // }
104 |
--------------------------------------------------------------------------------
/debugui/imgui_desktop.go:
--------------------------------------------------------------------------------
1 | //go:build !js
2 |
3 | package debugui
4 |
5 | import (
6 | "log"
7 |
8 | "github.com/inkyblackness/imgui-go/v4"
9 |
10 | "github.com/unitoftime/glitch"
11 | "github.com/unitoftime/glitch/internal/glfw"
12 | "github.com/unitoftime/glitch/internal/mainthread"
13 | )
14 |
15 | type GuiWindow interface {
16 | DisplaySize() [2]float32
17 | FramebufferSize() [2]float32
18 | AddMouseButtonCallback(glfw.MouseButtonCallback)
19 | AddScrollCallback(glfw.ScrollCallback)
20 | AddKeyCallback(glfw.KeyCallback)
21 | AddCharCallback(glfw.CharCallback)
22 | }
23 |
24 | type Gui struct {
25 | context *imgui.Context
26 | io imgui.IO
27 | renderer *OpenGL3
28 |
29 | mouseJustPressed [3]bool
30 | win *glitch.Window
31 | }
32 |
33 | func NewImgui(win *glitch.Window) *Gui {
34 | g := Gui{
35 | win: win,
36 | }
37 | mainthread.Call(func() {
38 | g.context = imgui.CreateContext(nil)
39 | g.io = imgui.CurrentIO()
40 |
41 | renderer, err := NewOpenGL3(g.io)
42 | if err != nil {
43 | log.Fatal(err)
44 | }
45 | g.renderer = renderer
46 |
47 | g.setKeyMapping()
48 | g.installCallbacks(win)
49 | })
50 | return &g
51 | }
52 |
53 | func (g *Gui) NewFrame() {
54 | mainthread.Call(func() {
55 | ds := g.win.DisplaySize()
56 | mouseX, mouseY := g.win.GetMouse()
57 |
58 | g.io.SetDisplaySize(imgui.Vec2{X: ds[0], Y: ds[1]})
59 | g.io.SetDeltaTime(float32(1 / 60.0))
60 | // TODO - before setting this check if window is focused
61 | g.io.SetMousePosition(imgui.Vec2{X: float32(mouseX), Y: float32(mouseY)})
62 |
63 | for i := 0; i < len(g.mouseJustPressed); i++ {
64 | down := g.mouseJustPressed[i] || (g.win.GetMouseButton(glfwButtonIDByIndex[i]) == glfw.Press)
65 | g.io.SetMouseButtonDown(i, down)
66 | g.mouseJustPressed[i] = false
67 | }
68 |
69 | imgui.NewFrame()
70 | })
71 | }
72 |
73 | func (g *Gui) Captured() bool {
74 | return g.io.WantCaptureMouse() || g.io.WantCaptureKeyboard()
75 | }
76 |
77 | func (g *Gui) Draw() {
78 | mainthread.Call(func() {
79 | imgui.Render()
80 |
81 | ds := g.win.DisplaySize()
82 | fs := g.win.FramebufferSize()
83 | g.renderer.Render(ds, fs, imgui.RenderedDrawData())
84 | })
85 | }
86 |
87 | func (g *Gui) Terminate() {
88 | mainthread.Call(func() {
89 | g.renderer.Dispose()
90 | g.context.Destroy()
91 | })
92 | }
93 |
94 | // --------------------------------------------------------------------------------
95 | // Most of this is taken directly from:
96 | // https://github.com/inkyblackness/imgui-go-examples/blob/master/internal/platforms/glfw.go
97 | // --------------------------------------------------------------------------------
98 | func (g *Gui) setKeyMapping() {
99 | // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
100 | g.io.KeyMap(imgui.KeyTab, int(glfw.KeyTab))
101 | g.io.KeyMap(imgui.KeyLeftArrow, int(glfw.KeyLeft))
102 | g.io.KeyMap(imgui.KeyRightArrow, int(glfw.KeyRight))
103 | g.io.KeyMap(imgui.KeyUpArrow, int(glfw.KeyUp))
104 | g.io.KeyMap(imgui.KeyDownArrow, int(glfw.KeyDown))
105 | g.io.KeyMap(imgui.KeyPageUp, int(glfw.KeyPageUp))
106 | g.io.KeyMap(imgui.KeyPageDown, int(glfw.KeyPageDown))
107 | g.io.KeyMap(imgui.KeyHome, int(glfw.KeyHome))
108 | g.io.KeyMap(imgui.KeyEnd, int(glfw.KeyEnd))
109 | g.io.KeyMap(imgui.KeyInsert, int(glfw.KeyInsert))
110 | g.io.KeyMap(imgui.KeyDelete, int(glfw.KeyDelete))
111 | g.io.KeyMap(imgui.KeyBackspace, int(glfw.KeyBackspace))
112 | g.io.KeyMap(imgui.KeySpace, int(glfw.KeySpace))
113 | g.io.KeyMap(imgui.KeyEnter, int(glfw.KeyEnter))
114 | g.io.KeyMap(imgui.KeyEscape, int(glfw.KeyEscape))
115 | g.io.KeyMap(imgui.KeyA, int(glfw.KeyA))
116 | g.io.KeyMap(imgui.KeyC, int(glfw.KeyC))
117 | g.io.KeyMap(imgui.KeyV, int(glfw.KeyV))
118 | g.io.KeyMap(imgui.KeyX, int(glfw.KeyX))
119 | g.io.KeyMap(imgui.KeyY, int(glfw.KeyY))
120 | g.io.KeyMap(imgui.KeyZ, int(glfw.KeyZ))
121 | }
122 |
123 | func (g *Gui) installCallbacks(win GuiWindow) {
124 | win.AddMouseButtonCallback(g.mouseButtonChange)
125 | win.AddScrollCallback(g.mouseScrollChange)
126 | win.AddKeyCallback(g.keyChange)
127 | win.AddCharCallback(g.charChange)
128 | }
129 |
130 | var glfwButtonIndexByID = map[glfw.MouseButton]int{
131 | glfw.MouseButton1: 0,
132 | glfw.MouseButton2: 1,
133 | glfw.MouseButton3: 2,
134 | }
135 |
136 | var glfwButtonIDByIndex = map[int]glfw.MouseButton{
137 | 0: glfw.MouseButton1,
138 | 1: glfw.MouseButton2,
139 | 2: glfw.MouseButton3,
140 | }
141 |
142 | func (g *Gui) mouseButtonChange(window *glfw.Window, rawButton glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) {
143 | buttonIndex, known := glfwButtonIndexByID[rawButton]
144 |
145 | if known && (action == glfw.Press) {
146 | g.mouseJustPressed[buttonIndex] = true
147 | }
148 | }
149 |
150 | func (g *Gui) mouseScrollChange(window *glfw.Window, x, y float64) {
151 | g.io.AddMouseWheelDelta(float32(x), float32(y))
152 | }
153 |
154 | func (g *Gui) keyChange(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
155 | if action == glfw.Press {
156 | g.io.KeyPress(int(key))
157 | }
158 | if action == glfw.Release {
159 | g.io.KeyRelease(int(key))
160 | }
161 |
162 | // Modifiers are not reliable across systems
163 | g.io.KeyCtrl(int(glfw.KeyLeftControl), int(glfw.KeyRightControl))
164 | g.io.KeyShift(int(glfw.KeyLeftShift), int(glfw.KeyRightShift))
165 | g.io.KeyAlt(int(glfw.KeyLeftAlt), int(glfw.KeyRightAlt))
166 | g.io.KeySuper(int(glfw.KeyLeftSuper), int(glfw.KeyRightSuper))
167 | }
168 |
169 | func (g *Gui) charChange(window *glfw.Window, char rune) {
170 | g.io.AddInputCharacters(string(char))
171 | }
172 |
173 | // // ClipboardText returns the current clipboard text, if available.
174 | // func (platform *GLFW) ClipboardText() (string, error) {
175 | // return platform.window.GetClipboardString()
176 | // }
177 |
178 | // // SetClipboardText sets the text as the current clipboard text.
179 | // func (platform *GLFW) SetClipboardText(text string) {
180 | // platform.window.SetClipboardString(text)
181 | // }
182 |
--------------------------------------------------------------------------------
/debugui/imgui_wasm.go:
--------------------------------------------------------------------------------
1 | //go:build js || wasm
2 |
3 | package debugui
4 |
5 | import (
6 | "github.com/unitoftime/glitch"
7 | "github.com/unitoftime/glitch/internal/glfw"
8 | )
9 |
10 | type GuiWindow interface {
11 | DisplaySize() [2]float32
12 | FramebufferSize() [2]float32
13 | AddMouseButtonCallback(glfw.MouseButtonCallback)
14 | AddScrollCallback(glfw.ScrollCallback)
15 | AddKeyCallback(glfw.KeyCallback)
16 | AddCharCallback(glfw.CharCallback)
17 | }
18 |
19 | type Gui struct {
20 | }
21 |
22 | func NewImgui(win GuiWindow) *Gui {
23 | return &Gui{}
24 | }
25 |
26 | func (g *Gui) NewFrame(win *glitch.Window) {
27 | }
28 |
29 | func (g *Gui) Draw(win *glitch.Window) {
30 | }
31 |
32 | func (g *Gui) Terminate() {
33 | }
34 |
35 | // --------------------------------------------------------------------------------
36 | // Most of this is taken directly from:
37 | // https://github.com/inkyblackness/imgui-go-examples/blob/master/internal/platforms/glfw.go
38 | // --------------------------------------------------------------------------------
39 | func (g *Gui) setKeyMapping() {
40 | }
41 |
42 | func (g *Gui) installCallbacks(win GuiWindow) {
43 | }
44 |
45 | func (g *Gui) mouseButtonChange(window *glfw.Window, rawButton glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) {
46 | }
47 |
48 | func (g *Gui) mouseScrollChange(window *glfw.Window, x, y float64) {
49 | }
50 |
51 | func (g *Gui) keyChange(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
52 | }
53 |
54 | func (g *Gui) charChange(window *glfw.Window, char rune) {
55 | }
56 |
57 | // // ClipboardText returns the current clipboard text, if available.
58 | // func (platform *GLFW) ClipboardText() (string, error) {
59 | // return platform.window.GetClipboardString()
60 | // }
61 |
62 | // // SetClipboardText sets the text as the current clipboard text.
63 | // func (platform *GLFW) SetClipboardText(text string) {
64 | // platform.window.SetClipboardString(text)
65 | // }
66 |
--------------------------------------------------------------------------------
/debugui/main.frag:
--------------------------------------------------------------------------------
1 | uniform sampler2D Texture;
2 |
3 | in vec2 Frag_UV;
4 | in vec4 Frag_Color;
5 |
6 | out vec4 Out_Color;
7 |
8 | void main()
9 | {
10 | Out_Color = vec4(Frag_Color.rgb, Frag_Color.a * texture(Texture, Frag_UV.st).r);
11 | }
--------------------------------------------------------------------------------
/debugui/main.vert:
--------------------------------------------------------------------------------
1 | uniform mat4 ProjMtx;
2 |
3 | in vec2 Position;
4 | in vec2 UV;
5 | in vec4 Color;
6 |
7 | out vec2 Frag_UV;
8 | out vec4 Frag_Color;
9 |
10 | void main()
11 | {
12 | Frag_UV = UV;
13 | Frag_Color = Color;
14 | gl_Position = ProjMtx * vec4(Position.xy, 0, 1);
15 | }
--------------------------------------------------------------------------------
/defaults.go:
--------------------------------------------------------------------------------
1 | package glitch
2 |
3 | import (
4 | _ "embed"
5 |
6 | "github.com/unitoftime/glitch/shaders"
7 | )
8 |
9 | // //go:embed shaders/sprite.vs
10 | // var spriteVertexShader string;
11 |
12 | // //go:embed shaders/sprite.fs
13 | // var spriteFragmentShader string;
14 |
15 | // var spriteShader = ShaderConfig{
16 | // VertexShader: spriteVertexShader,
17 | // FragmentShader: spriteFragmentShader,
18 | // VertexFormat: VertexFormat{
19 | // VertexAttribute("positionIn", AttrVec3, PositionXYZ),
20 | // VertexAttribute("colorIn", AttrVec4, ColorRGBA),
21 | // VertexAttribute("texCoordIn", AttrVec2, TexCoordXY),
22 | // },
23 | // UniformFormat: UniformFormat{
24 | // Attr{"model", AttrMat4},
25 | // Attr{"projection", AttrMat4},
26 | // Attr{"view", AttrMat4},
27 | // },
28 | // }
29 | // func VertexAttribute(name string, Type AttrType, swizzle SwizzleType) VertexAttr {
30 | // return VertexAttr{
31 | // Attr: Attr{
32 | // Name: name,
33 | // Type: Type,
34 | // },
35 | // Swizzle: swizzle,
36 | // }
37 | // }
38 |
39 | var defaultSpriteShader *Shader // Can set this to whatever you want
40 |
41 | func SetDefaultSpriteShader(shader *Shader) {
42 | defaultSpriteShader = shader
43 | }
44 |
45 | func GetDefaultSpriteShader() *Shader {
46 | if defaultSpriteShader != nil {
47 | return defaultSpriteShader
48 | }
49 |
50 | var err error
51 | defaultSpriteShader, err = NewShader(shaders.SpriteShader)
52 | if err != nil {
53 | panic(err)
54 | }
55 | return defaultSpriteShader
56 | }
57 |
58 | func DefaultMaterial(texture *Texture) Material {
59 | material := NewMaterial(GetDefaultSpriteShader())
60 | material.texture = texture
61 | return material
62 | }
63 |
64 | var defaultMsdfShader *Shader // Can set this to whatever you want
65 |
66 | func SetDefaultMsdfShader(shader *Shader) {
67 | defaultSpriteShader = shader
68 | }
69 |
70 | func GetDefaultMsdfShader() *Shader {
71 | if defaultMsdfShader != nil {
72 | return defaultMsdfShader
73 | }
74 |
75 | var err error
76 | defaultMsdfShader, err = NewShader(shaders.MSDFShader)
77 | if err != nil {
78 | panic(err)
79 | }
80 | return defaultMsdfShader
81 | }
82 |
83 | func DefaultMsdfMaterial(texture *Texture) Material {
84 | material := NewMaterial(GetDefaultMsdfShader())
85 | material.SetBlendMode(BlendModeNormal)
86 | material.texture = texture
87 | material.
88 | SetUniform("u_threshold", 0.5).
89 | SetUniform("u_outline_width_relative", 0.1).
90 | SetUniform("u_outline_blur", 0.0).
91 | SetUniform("u_outline_color", RGBA{0, 0, 0, 1})
92 | return material
93 | }
94 |
--------------------------------------------------------------------------------
/examples/3d/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | _ "image/png"
7 | "log"
8 | "math"
9 | "os"
10 | "runtime"
11 | "runtime/pprof"
12 | "time"
13 |
14 | "github.com/unitoftime/glitch"
15 | "github.com/unitoftime/glitch/shaders"
16 | )
17 |
18 | var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
19 | var memprofile = flag.String("memprofile", "", "write memory profile to `file`")
20 |
21 | func main() {
22 | flag.Parse()
23 | if *cpuprofile != "" {
24 | f, err := os.Create(*cpuprofile)
25 | if err != nil {
26 | log.Fatal("could not create CPU profile: ", err)
27 | }
28 | defer f.Close() // error handling omitted for example
29 | go func() {
30 | time.Sleep(10 * time.Second)
31 | if err := pprof.StartCPUProfile(f); err != nil {
32 | log.Fatal("could not start CPU profile: ", err)
33 | }
34 | }()
35 | defer pprof.StopCPUProfile()
36 | }
37 |
38 | glitch.Run(runGame)
39 |
40 | if *memprofile != "" {
41 | f, err := os.Create(*memprofile)
42 | if err != nil {
43 | log.Fatal("could not create memory profile: ", err)
44 | }
45 | defer f.Close() // error handling omitted for example
46 | runtime.GC() // get up-to-date statistics
47 | if err := pprof.WriteHeapProfile(f); err != nil {
48 | log.Fatal("could not write memory profile: ", err)
49 | }
50 | }
51 | }
52 |
53 | func runGame() {
54 | win, err := glitch.NewWindow(1920, 1080, "Glitch", glitch.WindowConfig{
55 | Vsync: true,
56 | Samples: 8,
57 | })
58 | if err != nil {
59 | panic(err)
60 | }
61 |
62 | diffuseShader, err := glitch.NewShader(shaders.DiffuseShader)
63 | if err != nil {
64 | panic(err)
65 | }
66 |
67 | // Text
68 | atlas, err := glitch.DefaultAtlas()
69 | if err != nil {
70 | panic(err)
71 | }
72 |
73 | text := atlas.Text("hello world", 1)
74 |
75 | diffuseMaterial := glitch.NewMaterial(diffuseShader)
76 | diffuseMaterial.SetDepthMode(glitch.DepthModeLess)
77 | diffuseMaterial.SetCullMode(glitch.CullModeNormal)
78 |
79 | diffuseMaterial.SetUniform("material.ambient", glitch.Vec3{1, 0.5, 0.31})
80 | diffuseMaterial.SetUniform("material.diffuse", glitch.Vec3{1, 0.5, 0.31})
81 | diffuseMaterial.SetUniform("material.specular", glitch.Vec3{1, 0.5, 0.31})
82 | diffuseMaterial.SetUniform("material.shininess", float32(32.0))
83 |
84 | diffuseMaterial.SetUniform("dirLight.direction", glitch.Vec3{0, 1, 0})
85 | diffuseMaterial.SetUniform("dirLight.ambient", glitch.Vec3{0.5, 0.5, 0.5})
86 | diffuseMaterial.SetUniform("dirLight.diffuse", glitch.Vec3{0.5, 0.5, 0.5})
87 | diffuseMaterial.SetUniform("dirLight.specular", glitch.Vec3{0.5, 0.5, 0.5})
88 |
89 | cube := glitch.NewModel(glitch.NewCubeMesh(50), diffuseMaterial)
90 |
91 | camera := glitch.NewCameraOrtho()
92 | pCam := glitch.NewCamera()
93 | start := time.Now()
94 |
95 | tt := 0.0
96 | var dt time.Duration
97 | for !win.Closed() {
98 | if win.Pressed(glitch.KeyEscape) {
99 | win.Close()
100 | }
101 | start = time.Now()
102 |
103 | camera.SetOrtho2D(win.Bounds())
104 | camera.SetView2D(0, 0, 1.0, 1.0)
105 |
106 | tt += dt.Seconds()
107 | // pCam.Position = glitch.Vec3{float32(100 * math.Cos(tt)), float32(100 * math.Sin(tt)), 50}
108 | pCam.Position = glitch.Vec3{100 * math.Cos(0), 100 * math.Sin(0), 50}
109 | pCam.Target = glitch.Vec3{0, 0, 0}
110 |
111 | pCam.SetPerspective(win)
112 | pCam.SetViewLookAt(win)
113 |
114 | glitch.Clear(win, glitch.RGBA{R: 0.1, G: 0.2, B: 0.3, A: 1.0})
115 |
116 | glitch.SetCameraMaterial(pCam.Material())
117 | diffuseShader.SetUniform("viewPos", pCam.Position) // TODO: This needs to be better encapsulated somehow?
118 | {
119 | mat := glitch.Mat4Ident
120 | mat.Scale(0.25, 0.25, 1.0).Translate(100, 100, 0)
121 |
122 | cubeMat := glitch.Mat4Ident
123 | cubeMat = *cubeMat.Translate(0, 0, 0).Rotate(float64(tt), glitch.Vec3{0, 0, 1})
124 | cube.Draw(win, cubeMat)
125 | }
126 |
127 | glitch.SetCamera(camera)
128 | {
129 | mat := glitch.Mat4Ident
130 | mat.Translate(0, 0, 0)
131 | text.Set(fmt.Sprintf("%2.2f ms", 1000*dt.Seconds()))
132 | text.DrawColorMask(win, mat, glitch.RGBA{R: 1.0, G: 1.0, B: 0.0, A: 1.0})
133 | }
134 |
135 | win.Update()
136 |
137 | dt = time.Since(start)
138 | // fmt.Println(dt.Seconds() * 1000)
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/examples/Makefile:
--------------------------------------------------------------------------------
1 | # linux:
2 | # go build -ldflags "-s" -v .
3 |
4 | # test:
5 | # go test -v .
6 |
7 | # TODO - tinyGO?
8 | wasm:
9 | # cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
10 | GOOS=js GOARCH=wasm go build -ldflags "-s" -o main.wasm
11 |
12 | windows:
13 | env GOOS=windows GOARCH=386 CGO_ENABLED=1 CXX=i686-w64-mingw32-g++ CC=i686-w64-mingw32-gcc go build -ldflags "-s" -v .
14 |
15 | compress:
16 | ls -lh main.wasm
17 | gzip -f --best -c main.wasm > main.wasm.gz
18 | ls -lh main.wasm.gz
19 |
20 | serve:
21 | ~/go/src/github.com/unitoftime/go-experiments/fileserver/fileserver
22 |
23 | run: wasm compress serve
24 |
--------------------------------------------------------------------------------
/examples/assets/assets.go:
--------------------------------------------------------------------------------
1 | package assets
2 |
3 | import (
4 | "embed"
5 | "encoding/json"
6 | "image"
7 | "image/draw"
8 | "io"
9 | )
10 |
11 | //go:embed *.png *.json
12 | var FS embed.FS
13 |
14 | func LoadImage(path string) (*image.NRGBA, error) {
15 | file, err := FS.Open(path)
16 | if err != nil {
17 | return nil, err
18 | }
19 | defer file.Close()
20 |
21 | img, _, err := image.Decode(file)
22 | if err != nil {
23 | return nil, err
24 | }
25 | bounds := img.Bounds()
26 | nrgba := image.NewNRGBA(image.Rect(0, 0, bounds.Dx(), bounds.Dy()))
27 | draw.Draw(nrgba, nrgba.Bounds(), img, bounds.Min, draw.Src)
28 | return nrgba, nil
29 | }
30 |
31 | func LoadJson(path string, v any) error {
32 | file, err := FS.Open(path)
33 | if err != nil {
34 | return err
35 | }
36 | defer file.Close()
37 |
38 | rawData, err := io.ReadAll(file)
39 | if err != nil {
40 | return err
41 | }
42 |
43 | return json.Unmarshal(rawData, v)
44 | }
45 |
--------------------------------------------------------------------------------
/examples/assets/atlas-msdf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unitoftime/glitch/3f1e6b9f26720479c972c8d14cdee984d7653405/examples/assets/atlas-msdf.png
--------------------------------------------------------------------------------
/examples/assets/atlas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unitoftime/glitch/3f1e6b9f26720479c972c8d14cdee984d7653405/examples/assets/atlas.png
--------------------------------------------------------------------------------
/examples/assets/button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unitoftime/glitch/3f1e6b9f26720479c972c8d14cdee984d7653405/examples/assets/button.png
--------------------------------------------------------------------------------
/examples/assets/button_hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unitoftime/glitch/3f1e6b9f26720479c972c8d14cdee984d7653405/examples/assets/button_hover.png
--------------------------------------------------------------------------------
/examples/assets/button_press.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unitoftime/glitch/3f1e6b9f26720479c972c8d14cdee984d7653405/examples/assets/button_press.png
--------------------------------------------------------------------------------
/examples/assets/gopher-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unitoftime/glitch/3f1e6b9f26720479c972c8d14cdee984d7653405/examples/assets/gopher-small.png
--------------------------------------------------------------------------------
/examples/assets/gopher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unitoftime/glitch/3f1e6b9f26720479c972c8d14cdee984d7653405/examples/assets/gopher.png
--------------------------------------------------------------------------------
/examples/assets/panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unitoftime/glitch/3f1e6b9f26720479c972c8d14cdee984d7653405/examples/assets/panel.png
--------------------------------------------------------------------------------
/examples/assets/panel_inner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unitoftime/glitch/3f1e6b9f26720479c972c8d14cdee984d7653405/examples/assets/panel_inner.png
--------------------------------------------------------------------------------
/examples/controller/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | _ "image/png"
6 |
7 | "github.com/unitoftime/flow/glm"
8 | "github.com/unitoftime/glitch"
9 | "github.com/unitoftime/glitch/examples/assets"
10 | )
11 |
12 | func check(err error) {
13 | if err != nil {
14 | panic(err)
15 | }
16 | }
17 |
18 | func main() {
19 | glitch.Run(run)
20 | }
21 |
22 | func run() {
23 | win, err := glitch.NewWindow(1920, 1080, "Glitch Demo", glitch.WindowConfig{
24 | Vsync: true,
25 | })
26 | check(err)
27 |
28 | img, err := assets.LoadImage("gopher.png")
29 | check(err)
30 |
31 | texture := glitch.NewTexture(img, false)
32 | sprite := glitch.NewSprite(texture, texture.Bounds())
33 |
34 | // atlasImg, err := assets.LoadImage("atlas-msdf.png")
35 | // check(err)
36 | // atlasJson := glitch.SdfAtlas{}
37 | // err = assets.LoadJson("atlas-msdf.json", &atlasJson)
38 | // check(err)
39 | // atlas, err := glitch.AtlasFromSdf(atlasJson, atlasImg, 1.0)
40 | // check(err)
41 |
42 | // text := atlas.Text("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 1.0)
43 | // text.Material().SetUniform("u_threshold", 0.6) // TODO: Should mostly come from default
44 |
45 | // A screenspace camera
46 | camera := glitch.NewCameraOrtho()
47 |
48 | for !win.Closed() {
49 | if win.Pressed(glitch.KeyEscape) {
50 | win.Close()
51 | }
52 |
53 | primaryGamepad := win.GetPrimaryGamepad()
54 | for b := glitch.ButtonFirst; b <= glitch.ButtonLast; b++ {
55 | if win.GetGamepadJustPressed(primaryGamepad, b) {
56 | fmt.Println("Just Pressed:", b)
57 | }
58 |
59 | if win.GetGamepadPressed(primaryGamepad, b) {
60 | fmt.Println("Pressed:", b)
61 | }
62 | }
63 |
64 | camera.SetOrtho2D(win.Bounds())
65 | camera.SetView2D(0, 0, 1, 1)
66 | glitch.SetCamera(camera)
67 |
68 | glitch.Clear(win, glm.Greyscale(0.5))
69 |
70 | // mat := glitch.Mat4Ident
71 | // text.Draw(win, *mat.Translate(100, 100, 0))
72 |
73 | center := win.Bounds().Center()
74 |
75 | {
76 | leftX := win.GetGamepadAxis(primaryGamepad, glitch.AxisLeftX)
77 | leftY := -win.GetGamepadAxis(primaryGamepad, glitch.AxisLeftY)
78 |
79 | axisLeft := glm.Vec2{leftX, leftY}.Scaled(100)
80 |
81 | mat := glitch.Mat4Ident
82 | mat.Translate(center.X-200, center.Y, 0)
83 | mat.Translate(axisLeft.X, axisLeft.Y, 0)
84 | sprite.Draw(win, mat)
85 | }
86 |
87 | {
88 | rightX := win.GetGamepadAxis(primaryGamepad, glitch.AxisRightX)
89 | rightY := -win.GetGamepadAxis(primaryGamepad, glitch.AxisRightY)
90 |
91 | axisRight := glm.Vec2{rightX, rightY}.Scaled(100)
92 |
93 | mat := glitch.Mat4Ident
94 | mat.Translate(center.X+200, center.Y, 0)
95 | mat.Translate(axisRight.X, axisRight.Y, 0)
96 | sprite.Draw(win, mat)
97 | }
98 |
99 | // Left Trigger
100 | {
101 | leftTrigger := win.GetGamepadAxis(primaryGamepad, glitch.AxisLeftTrigger)
102 |
103 | mat := glitch.Mat4Ident
104 | mat.Translate(center.X-400, center.Y, 0)
105 | mat.Translate(0, leftTrigger*100, 0)
106 | sprite.Draw(win, mat)
107 | }
108 |
109 | // Right Trigger
110 | {
111 | rightTrigger := win.GetGamepadAxis(primaryGamepad, glitch.AxisRightTrigger)
112 |
113 | mat := glitch.Mat4Ident
114 | mat.Translate(center.X+400, center.Y, 0)
115 | mat.Translate(0, rightTrigger*100, 0)
116 | sprite.Draw(win, mat)
117 | }
118 |
119 | win.Update()
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/examples/frame/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | _ "image/png"
6 | "math/rand"
7 | "time"
8 |
9 | "github.com/unitoftime/flow/glm"
10 | "github.com/unitoftime/glitch"
11 | "github.com/unitoftime/glitch/examples/assets"
12 | )
13 |
14 | func main() {
15 | glitch.Run(runGame)
16 | }
17 |
18 | func runGame() {
19 | win, err := glitch.NewWindow(1920, 1080, "Glitch - Framebuffer", glitch.WindowConfig{
20 | Vsync: false,
21 | })
22 | if err != nil {
23 | panic(err)
24 | }
25 |
26 | // shader, err := glitch.NewShader(shaders.SpriteShader)
27 | // if err != nil {
28 | // panic(err)
29 | // }
30 |
31 | manImage, err := assets.LoadImage("gopher.png")
32 | if err != nil {
33 | panic(err)
34 | }
35 | texture := glitch.NewTexture(manImage, false)
36 |
37 | x := 0.0
38 | y := 0.0
39 | manSprite := glitch.NewSprite(texture, glm.R(x, y, x+160, y+200))
40 |
41 | length := 100000
42 | man := make([]Man, length)
43 | for i := range man {
44 | man[i] = NewMan()
45 | }
46 |
47 | w := 160.0 / 4.0
48 | h := 200.0 / 4.0
49 |
50 | // Text
51 | atlas, err := glitch.DefaultAtlas()
52 | if err != nil {
53 | panic(err)
54 | }
55 |
56 | text := atlas.Text("hello world", 1)
57 |
58 | fmt.Println(win.Bounds())
59 | frame := glitch.NewFrame(win.Bounds(), false)
60 |
61 | camera := glitch.NewCameraOrtho()
62 | start := time.Now()
63 | var dt time.Duration
64 | for !win.Closed() {
65 | if win.Pressed(glitch.KeyEscape) {
66 | win.Close()
67 | }
68 | camera.SetOrtho2D(win.Bounds())
69 | camera.SetView2D(0, 0, 1, 1)
70 | glitch.SetCamera(camera)
71 |
72 | start = time.Now()
73 | for i := range man {
74 | man[i].position.X += man[i].velocity.X
75 | man[i].position.Y += man[i].velocity.Y
76 |
77 | if man[i].position.X <= 0 || (man[i].position.X+w) >= float64(1920) {
78 | man[i].velocity.X = -man[i].velocity.X
79 | }
80 | if man[i].position.Y <= 0 || (man[i].position.Y+h) >= float64(1080) {
81 | man[i].velocity.Y = -man[i].velocity.Y
82 | }
83 | }
84 |
85 | glitch.Clear(frame, glm.Greyscale(0.5))
86 |
87 | for i := range man {
88 | mat := glitch.Mat4Ident
89 | // mat.Scale(0.25, 0.25, 1.0).Translate(man[i].position.X, man[i].position.Y, -man[i].position.Y)
90 | mat.Scale(0.25, 0.25, 1.0).Translate(man[i].position.X, man[i].position.Y, 0)
91 |
92 | // mesh.DrawColorMask(pass, mat, glitch.RGBA{0.5, 1.0, 1.0, 1.0})
93 | // pass.SetLayer(man[i].layer)
94 | manSprite.DrawColorMask(frame, mat, man[i].color)
95 | // manSprite.DrawColorMask(pass, mat, glitch.RGBA{1.0, 1.0, 1.0, 1.0})
96 | }
97 |
98 | mat := glitch.Mat4Ident
99 |
100 | text.Set(fmt.Sprintf("%2.2f ms", 1000*dt.Seconds()))
101 | text.DrawColorMask(frame, mat, glitch.Black)
102 |
103 | glitch.Clear(win, glm.White)
104 | frame.Draw(win, glitch.Mat4Ident)
105 | win.Update()
106 |
107 | dt = time.Since(start)
108 | }
109 | }
110 |
111 | type Man struct {
112 | position, velocity glitch.Vec2
113 | color glitch.RGBA
114 | layer int8
115 | }
116 |
117 | func NewMan() Man {
118 | colors := []glitch.RGBA{
119 | glitch.RGBA{R: 1.0, G: 0, B: 0, A: 1.0},
120 | glitch.RGBA{R: 0, G: 1.0, B: 0, A: 1.0},
121 | glitch.RGBA{R: 0, G: 0, B: 1.0, A: 1.0},
122 | }
123 | randIndex := rand.Intn(len(colors))
124 | vScale := 5.0
125 | return Man{
126 | // position: mgl32.Vec2{100, 100},
127 | // position: mgl32.Vec2{float32(float64(width/2) * rand.Float64()),
128 | // float32(float64(height/2) * rand.Float64())},
129 | position: glitch.Vec2{1920 / 2, 1080 / 2},
130 | velocity: glitch.Vec2{float64(2 * vScale * (rand.Float64() - 0.5)),
131 | float64(2 * vScale * (rand.Float64() - 0.5))},
132 | color: colors[randIndex],
133 | layer: int8(randIndex) + 1,
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/examples/gophermark/Makefile:
--------------------------------------------------------------------------------
1 | # linux:
2 | # go build -ldflags "-s" -v .
3 |
4 | # test:
5 | # go test -v .
6 |
7 | # TODO - tinyGO?
8 | tinygo:
9 | tinygo build -o main.wasm -target wasm ./main.go
10 | wasm:
11 | # cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
12 | GOOS=js GOARCH=wasm go build -ldflags "-s" -o main.wasm
13 |
14 | windows:
15 | env GOOS=windows GOARCH=386 CGO_ENABLED=1 CXX=i686-w64-mingw32-g++ CC=i686-w64-mingw32-gcc go build -ldflags "-s" -v .
16 |
17 | compress:
18 | ls -lh main.wasm
19 | gzip -f --best -c main.wasm > main.wasm.gz
20 | ls -lh main.wasm.gz
21 |
22 | serve:
23 | serve
24 |
25 | run: wasm compress serve
26 |
--------------------------------------------------------------------------------
/examples/gophermark/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Try: https://www.shadertoy.com/view/csX3RH
4 |
5 | import (
6 | "flag"
7 | "fmt"
8 | _ "image/png"
9 | "log"
10 | "math/rand"
11 | "os"
12 | "runtime"
13 | "runtime/pprof"
14 | "time"
15 |
16 | "github.com/unitoftime/glitch"
17 | "github.com/unitoftime/glitch/examples/assets"
18 | )
19 |
20 | var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
21 | var memprofile = flag.String("memprofile", "", "write memory profile to `file`")
22 |
23 | func main() {
24 | flag.Parse()
25 | if *cpuprofile != "" {
26 | f, err := os.Create(*cpuprofile)
27 | if err != nil {
28 | log.Fatal("could not create CPU profile: ", err)
29 | }
30 | defer f.Close() // error handling omitted for example
31 | go func() {
32 | if err := pprof.StartCPUProfile(f); err != nil {
33 | log.Fatal("could not start CPU profile: ", err)
34 | }
35 | }()
36 | defer pprof.StopCPUProfile()
37 | }
38 |
39 | glitch.Run(runGame)
40 |
41 | if *memprofile != "" {
42 | f, err := os.Create(*memprofile)
43 | if err != nil {
44 | log.Fatal("could not create memory profile: ", err)
45 | }
46 | defer f.Close() // error handling omitted for example
47 | runtime.GC() // get up-to-date statistics
48 | if err := pprof.WriteHeapProfile(f); err != nil {
49 | log.Fatal("could not write memory profile: ", err)
50 | }
51 | }
52 | }
53 |
54 | func runGame() {
55 | win, err := glitch.NewWindow(1920, 1080, "Glitch - Gophermark", glitch.WindowConfig{
56 | // Vsync: true,
57 | })
58 | if err != nil {
59 | panic(err)
60 | }
61 |
62 | // shader, err := glitch.NewShader(shaders.SpriteShader)
63 | // if err != nil {
64 | // panic(err)
65 | // }
66 |
67 | // pass := glitch.NewRenderPass(shader)
68 | // pass.DepthTest = true
69 |
70 | manImage, err := assets.LoadImage("gopher.png")
71 | if err != nil {
72 | panic(err)
73 | }
74 | texture := glitch.NewTexture(manImage, false)
75 | manSprite := glitch.NewSprite(texture, texture.Bounds())
76 |
77 | // length := 25_000 // With no sort
78 | length := 6_250 // With SoftwareSort
79 | man := make([]Man, length)
80 | for i := range man {
81 | man[i] = NewMan()
82 | }
83 |
84 | w := float64(160.0) / 4
85 | h := float64(200.0) / 4
86 |
87 | // Text
88 | atlas, err := glitch.DefaultAtlas()
89 | if err != nil {
90 | panic(err)
91 | }
92 |
93 | text := atlas.Text("", 1)
94 |
95 | min := time.Duration(0)
96 | max := time.Duration(0)
97 |
98 | counter := 0
99 | camera := glitch.NewCameraOrtho()
100 | // camera.DepthRange = glitch.Vec2{-127, 127}
101 |
102 | start := time.Now()
103 | var dt time.Duration
104 |
105 | // geom := glitch.NewGeomDraw()
106 | // geomRect := glitch.R(-16, -16, 16, 16)
107 | // geomMesh := glitch.NewQuadMesh(geomRect, geomRect)
108 |
109 | sorter := glitch.NewSorter()
110 | sorter.SoftwareSort = glitch.SoftwareSortY
111 | // sorter.DepthTest = true
112 |
113 | mat := glitch.Mat4Ident
114 | for !win.Closed() {
115 | if win.Pressed(glitch.KeyEscape) {
116 | win.Close()
117 | }
118 | start = time.Now()
119 |
120 | counter = (counter + 1) % 60
121 |
122 | for i := range man {
123 | man[i].position.X += man[i].velocity.X
124 | man[i].position.Y += man[i].velocity.Y
125 |
126 | if man[i].position.X <= 0 || (man[i].position.X+w) >= float64(1920) {
127 | man[i].velocity.X = -man[i].velocity.X
128 | }
129 | if man[i].position.Y <= 0 || (man[i].position.Y+h) >= float64(1080) {
130 | man[i].velocity.Y = -man[i].velocity.Y
131 | }
132 | }
133 |
134 | camera.SetOrtho2D(win.Bounds())
135 | camera.SetView2D(0, 0, 1.0, 1.0)
136 | glitch.SetCamera(camera)
137 |
138 | glitch.Clear(win, glitch.RGBA{R: 0.1, G: 0.2, B: 0.3, A: 1.0})
139 |
140 | // geom.Clear()
141 | for i := range man {
142 | mat = glitch.Mat4Ident
143 | mat.Scale(0.25, 0.25, 1.0).Translate(man[i].position.X, man[i].position.Y, 0)
144 | manSprite.DrawColorMask(sorter, mat, man[i].color)
145 | // geom.DrawRect(pass, geomRect, mat, man[i].color)
146 | // geomMesh.DrawColorMask(pass, mat, man[i].color)
147 | // geom.DrawRect2(geomRect, mat, man[i].color)
148 | }
149 | // geom.Draw(pass, glitch.Mat4Ident)
150 |
151 | if counter == 0 {
152 | text.Clear()
153 | text.Set(fmt.Sprintf("%2.2f (%2.2f, %2.2f) ms",
154 | 1000*dt.Seconds(),
155 | 1000*min.Seconds(),
156 | 1000*max.Seconds()))
157 | min = 100000000000
158 | max = 0
159 |
160 | // metrics := glitch.GetMetrics()
161 | // fmt.Printf("%+v\n", metrics)
162 | }
163 |
164 | sorter.Draw(win)
165 | text.DrawColorMask(win, glitch.Mat4Ident, glitch.White)
166 |
167 | win.Update()
168 |
169 | dt = time.Since(start)
170 |
171 | // dt = time.Since(start)
172 | if dt > max {
173 | max = dt
174 | }
175 | if dt < min {
176 | min = dt
177 | }
178 | // fmt.Println(dt.Seconds() * 1000)
179 | }
180 | }
181 |
182 | type Man struct {
183 | position, velocity glitch.Vec2
184 | color glitch.RGBA
185 | layer uint8
186 | }
187 |
188 | func NewMan() Man {
189 | colors := []glitch.RGBA{
190 | glitch.RGBA{R: 1.0, G: 0, B: 0, A: 1.0},
191 | glitch.RGBA{R: 0, G: 1.0, B: 0, A: 1.0},
192 | glitch.RGBA{R: 0, G: 0, B: 1.0, A: 1.0},
193 | }
194 | randIndex := rand.Intn(len(colors))
195 | vScale := 5.0
196 | return Man{
197 | // position: mgl32.Vec2{100, 100},
198 | // position: mgl32.Vec2{float32(float64(width/2) * rand.Float64()),
199 | // float32(float64(height/2) * rand.Float64())},
200 | position: glitch.Vec2{1920 / 2, 1080 / 2},
201 | velocity: glitch.Vec2{float64(2 * vScale * (rand.Float64() - 0.5)),
202 | float64(2 * vScale * (rand.Float64() - 0.5))},
203 | color: colors[randIndex],
204 | layer: uint8(randIndex) + 1,
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/examples/gophermark/slicegen_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "math/rand"
5 | "reflect"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func InterfaceSlice(slice interface{}) []interface{} {
11 | s := reflect.ValueOf(slice)
12 | if s.Kind() != reflect.Slice {
13 | panic("InterfaceSlice() given a non-slice type")
14 | }
15 | // Keep the distinction between nil and empty slice input
16 | if s.IsNil() {
17 | return nil
18 | }
19 | ret := make([]interface{}, s.Len())
20 | for i := 0; i < s.Len(); i++ {
21 | ret[i] = s.Index(i).Interface()
22 | }
23 | return ret
24 | }
25 |
26 | type TestStruct struct {
27 | name string
28 | age int
29 | }
30 |
31 | var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
32 |
33 | func randSeq(n int) string {
34 | b := make([]rune, n)
35 | for i := range b {
36 | b[i] = letters[rand.Intn(len(letters))]
37 | }
38 | return string(b)
39 | }
40 |
41 | func randTestStruct(lenArray int, lenMap int) map[int][]TestStruct {
42 | randomStructMap := make(map[int][]TestStruct, lenMap)
43 | for i := 0; i < lenMap; i++ {
44 | var testStructs = make([]TestStruct, 0)
45 | for k := 0; k < lenArray; k++ {
46 | rand.Seed(time.Now().UnixNano())
47 | randomString := randSeq(10)
48 | randomInt := rand.Intn(100)
49 | testStructs = append(testStructs, TestStruct{name: randomString, age: randomInt})
50 | }
51 | randomStructMap[i] = testStructs
52 | }
53 | return randomStructMap
54 | }
55 |
56 | // func BenchmarkLoopConversion(b *testing.B) {
57 | // var testStructMap = randTestStruct(10, 100)
58 | // b.ResetTimer()
59 | // for i := 0; i < b.N; i++ {
60 | // obj := make([]interface{}, len(testStructMap[i%100]))
61 | // for k := range testStructMap[i%100] {
62 | // obj[k] = testStructMap[i%100][k]
63 | // }
64 | // }
65 | // }
66 |
67 | func newSlice(size int) []float64 {
68 | return make([]float64, size)
69 | }
70 |
71 | func newSliceGeneric[T any](size int) []T {
72 | return make([]T, size)
73 | }
74 |
75 | func conv(s []float64) interface{} {
76 | return s[0:5]
77 | }
78 |
79 | func convGen[T any](s []T) interface{} {
80 | return s[0:5]
81 | }
82 |
83 | func BenchmarkRegularSlice(b *testing.B) {
84 | slice := newSlice(1e9)
85 | b.ResetTimer()
86 |
87 | for i := 0; i < b.N; i++ {
88 | iSlice := conv(slice)
89 |
90 | switch s := iSlice.(type) {
91 | case []float64:
92 | s[0] = s[0] + 1
93 | default:
94 | panic("ERROR")
95 | }
96 | }
97 | }
98 |
99 | func BenchmarkRegularSliceGeneric(b *testing.B) {
100 | slice := newSliceGeneric[float64](1e9)
101 | b.ResetTimer()
102 |
103 | for i := 0; i < b.N; i++ {
104 | iSlice := convGen[float64](slice)
105 |
106 | switch s := iSlice.(type) {
107 | case []float64:
108 | s[0] = s[0] + 1
109 | default:
110 | panic("ERROR")
111 | }
112 | }
113 | }
114 |
115 | // func BenchmarkRegularSliceConv(b *testing.B) {
116 | // slice := make([]float64, b.N)
117 | // var iSlice interface{}
118 |
119 | // b.ResetTimer()
120 |
121 | // for i := 0; i < b.N; i++ {
122 | // iSlice = slice
123 |
124 | // switch s := iSlice.(type) {
125 | // case []float64:
126 | // s[0] = s[0] + 1
127 | // default:
128 | // panic("ERROR")
129 | // }
130 | // }
131 | // }
132 |
133 | // func BenchmarkGenericSliceConv(b *testing.B) {
134 | // slice := make([]float64, b.N)
135 | // var iSlice interface{}
136 |
137 | // b.ResetTimer()
138 |
139 | // for i := 0; i < b.N; i++ {
140 | // iSlice = slice
141 |
142 | // switch s := iSlice.(type) {
143 | // case []float64:
144 | // s[0] = s[0] + 1
145 | // default:
146 | // panic("ERROR")
147 | // }
148 | // }
149 | // }
150 |
--------------------------------------------------------------------------------
/examples/graph/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "time"
7 |
8 | "github.com/unitoftime/flow/glm"
9 | "github.com/unitoftime/glitch"
10 | "github.com/unitoftime/glitch/graph"
11 | // "github.com/unitoftime/glitch/ui"
12 | )
13 |
14 | func check(err error) {
15 | if err != nil {
16 | panic(err)
17 | }
18 | }
19 |
20 | func main() {
21 | fmt.Println("Starting")
22 | glitch.Run(runGame)
23 | }
24 |
25 | func runGame() {
26 | win, err := glitch.NewWindow(1920, 1080, "Glitch - Graph Demo", glitch.WindowConfig{
27 | Vsync: true,
28 | Samples: 4,
29 | })
30 | check(err)
31 |
32 | // shader, err := glitch.NewShader(shaders.SpriteShader)
33 | // if err != nil {
34 | // panic(err)
35 | // }
36 |
37 | dat := make([]glitch.Vec2, 0)
38 | for i := 0; i < 1000; i++ {
39 | dat = append(dat, glitch.Vec2{float64(i) / 100.0, float64(math.Sin(float64(i) / 100.0))})
40 | }
41 |
42 | // lightBlue := glitch.RGBA{0x8a, 0xeb, 0xf1, 0xff}
43 | // pink := color.NRGBA{0xcd, 0x60, 0x93, 0xff}
44 |
45 | // pad := float32(50)
46 | // rect := glitch.R(0 + pad, 0 + pad, 1920 - pad, 1080 - pad)
47 | // rect := glitch.R(0, 0, 1, 1)
48 | rect := win.Bounds()
49 | rect = glm.R(rect.Min.X, rect.Min.Y, rect.Min.X+500, rect.Min.Y+500)
50 |
51 | graph := graph.NewGraph(rect)
52 |
53 | camera := glitch.NewCameraOrtho()
54 |
55 | dt := 15 * time.Millisecond
56 | index := 0
57 | start := time.Now()
58 | for !win.Pressed(glitch.KeyEscape) {
59 | camera.SetOrtho2D(win.Bounds())
60 | camera.SetView2D(0, 0, 1, 1)
61 | glitch.SetCamera(camera)
62 |
63 | glitch.Clear(win, glm.Black)
64 |
65 | mat := glitch.Mat4Ident
66 | // mat = *mat.Scale(100, 100, 100)
67 | graph.Clear()
68 | graph.Line(dat)
69 | graph.DrawColorMask(win, mat, glitch.RGBA{0, 1, 1, 1})
70 |
71 | win.Update()
72 |
73 | dt = time.Since(start)
74 | dat[index].Y = float64(dt.Seconds())
75 | index = (index + 1) % len(dat)
76 | start = time.Now()
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/examples/idea.go:
--------------------------------------------------------------------------------
1 | package idea
2 |
3 | /*
4 |
5 | // Sorting
6 | // target | shader | translucency type | material | depth | mesh
7 |
8 | func idea() {
9 | // Create a window, which implements some 'target' interface (ie something that can be rendered to)
10 | win, err := glitch.NewWindow(1920, 1080, "Glitch", glitch.WindowConfig{
11 | Vsync: true,
12 | })
13 | if err != nil { panic(err) }
14 |
15 | // Create a shader program with specified attributes and uniforms
16 | attrFmt := glitch.VertexFormat{
17 | glitch.Attrib{"aPos", glitch.AttrVec3},
18 | glitch.Attrib{"aColor", glitch.AttrVec3},
19 | glitch.Attrib{"aTexCoord", glitch.AttrVec2},
20 | }
21 | uniformFmt := glitch.AttributeFormat{
22 | glitch.Attrib{"projection", glitch.AttrMat4},
23 | glitch.Attrib{"transform", glitch.AttrMat4},
24 | }
25 | shader, err := glitch.NewShader(vertexSource, fragmentSource, attrFmt, uniformFmt)
26 | if err != nil { panic(err) }
27 |
28 | // Load Textures
29 | manImage, err := loadImage("man.png")
30 | if err != nil {
31 | panic(err)
32 | }
33 | texture := glitch.NewTexture(160, 200, manImage.Pix)
34 |
35 | // Create Meshes
36 | // meshData, err := loadGltf("man.gltf")
37 | // mesh := glitch.NewMesh(meshData)
38 | mesh := glitch.NewMesh(geometry.Quad())
39 |
40 | // Create Text
41 | // Any way to combine this into quad and stuff like that? Or is it different enough?
42 | // fontData, err := loadText("font.ttf")
43 | // glitch.NewFont(fontData)
44 |
45 | // Draw stuff
46 | // Option 1 - more stateful
47 | glitch.SetTarget(win)
48 | glitch.SetShader(shader)
49 | glitch.SetUniform("transform", identMat)
50 | glitch.SetUniform("projection", identMat)
51 |
52 | for !win.Closed() {
53 |
54 | glitch.Draw(mesh, matrix) // Draws but transforms verts via matrix
55 |
56 | matrix := identMatrix
57 | glitch.DrawColorMask(mesh, matrix, color) // Draws but transforms verts via matrix and colors via color
58 | // What if people want to modify other vertex attributes?
59 |
60 | // Essentially
61 | {
62 | // Setup shader params
63 | glitch.SetUniform("transform", identMat)
64 | glitch.SetUniform("projection", identMat)
65 | // pass in data into that same context
66 | glitch.Draw(mesh, matrix)
67 | // 1. extra parameters let you modify a few special attributes
68 | }
69 |
70 | {
71 | glitch.SetShader(2dShader)
72 | glitch.SetUniform("myUniform", uniformValue)
73 |
74 | glitch.SetTarget(win)
75 | glitch.Draw(mesh, matrix)
76 |
77 | glitch.SetTarget(myFramebuffer)
78 | glitch.Draw(mesh, matrix)
79 | }
80 |
81 | {
82 | glitch.SetShader(textShader)
83 | glitch.SetTarget(win)
84 | glitch.Draw(mesh, matrix)
85 | }
86 | }
87 |
88 | // Option 2 - More object based? Shader being the main thing you draw against
89 | shader.SetTarget(win)
90 | shader.SetTexture(0, texture)
91 | shader.SetUniform("transform", mat)
92 | shader.SetUniform("projection", mat)
93 | shader.Draw(mesh, matrix)
94 | shader.Execute()
95 |
96 |
97 | // Option 3 - Pass Based
98 | pass := shader.NewRenderPass(win) // rendering to a target
99 |
100 | pass.Clear()
101 | pass.SetTexture(0, texture)
102 | pass.SetUniform("transform", mat)
103 | pass.SetUniform("projection", mat)
104 | pass.Draw(mesh, matrix)
105 | pass.Execute()
106 | pass.Execute(win, )
107 |
108 | pass.Clear()
109 | mesh.Draw(pass, matrix)
110 |
111 | glitch.SetTarget(win)
112 | glitch.SetShader(shader)
113 | {
114 | identMat := mgl32.Mat4{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}
115 | shader.SetUniform("transform", identMat)
116 |
117 | projMat := mgl32.Ortho2D(0, float32(1920), 0, float32(1080))
118 | shader.SetUniform("projection", projMat)
119 | }
120 |
121 | for !win.ShouldClose() {
122 | if win.Pressed(glitch.KeyBackspace) {
123 | win.Close()
124 | }
125 |
126 | glitch.Clear(glitch.RGBA{0.1, 0.2, 0.3, 1.0})
127 |
128 | glitch.SetTexture(0, texture)
129 | glitch.Draw(mesh)
130 |
131 | glitch.FinalizeDraw()
132 |
133 | win.Update() // SwapBuffers, PollEvents
134 | }
135 | }
136 | */
137 |
--------------------------------------------------------------------------------
/examples/imgui/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "time"
7 |
8 | "github.com/unitoftime/flow/glm"
9 | "github.com/unitoftime/glitch"
10 | "github.com/unitoftime/glitch/graph"
11 |
12 | "github.com/inkyblackness/imgui-go/v4"
13 | debugui "github.com/unitoftime/glitch/debugui"
14 | )
15 |
16 | func check(err error) {
17 | if err != nil {
18 | panic(err)
19 | }
20 | }
21 |
22 | func main() {
23 | fmt.Println("Starting")
24 | glitch.Run(runGame)
25 | }
26 |
27 | func runGame() {
28 | win, err := glitch.NewWindow(1920, 1080, "Glitch - Graph Demo", glitch.WindowConfig{
29 | Vsync: true,
30 | Samples: 4,
31 | })
32 | check(err)
33 |
34 | gui := debugui.NewImgui(win)
35 |
36 | dat := make([]glitch.Vec2, 0)
37 | for i := 0; i < 1000; i++ {
38 | dat = append(dat, glitch.Vec2{float64(i) / 100.0, float64(math.Sin(float64(i) / 100.0))})
39 | }
40 |
41 | // lightBlue := glitch.RGBA{0x8a, 0xeb, 0xf1, 0xff}
42 | // pink := color.NRGBA{0xcd, 0x60, 0x93, 0xff}
43 |
44 | // pad := float32(50)
45 | // rect := glitch.R(0 + pad, 0 + pad, 1920 - pad, 1080 - pad)
46 | // rect := glitch.R(0, 0, 1, 1)
47 | rect := win.Bounds()
48 | rect = glm.R(rect.Min.X, rect.Min.Y, rect.Min.X+500, rect.Min.Y+500)
49 |
50 | graph := graph.NewGraph(rect)
51 |
52 | camera := glitch.NewCameraOrtho()
53 |
54 | text := "test"
55 |
56 | dt := 15 * time.Millisecond
57 | index := 0
58 | start := time.Now()
59 |
60 | for !win.Pressed(glitch.KeyEscape) {
61 | camera.SetOrtho2D(win.Bounds())
62 | camera.SetView2D(0, 0, 1, 1)
63 | glitch.SetCamera(camera)
64 |
65 | glitch.Clear(win, glitch.RGBA{0, 0, 0, 1.0})
66 |
67 | mat := glitch.Mat4Ident
68 | // mat = *mat.Scale(100, 100, 100)
69 | graph.Clear()
70 | graph.Line(dat)
71 | graph.DrawColorMask(win, mat, glitch.RGBA{0, 1, 1, 1})
72 |
73 | gui.NewFrame()
74 | imgui.Begin("test")
75 | imgui.InputText("Thing", &text)
76 | imgui.End()
77 | gui.Draw()
78 |
79 | win.Update()
80 |
81 | dt = time.Since(start)
82 | dat[index].Y = float64(dt.Seconds())
83 | index = (index + 1) % len(dat)
84 | start = time.Now()
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/examples/pixelart/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Try: https://www.shadertoy.com/view/csX3RH
4 |
5 | import (
6 | "fmt"
7 | _ "image/png"
8 | "math"
9 | "math/rand"
10 | "time"
11 |
12 | "github.com/unitoftime/glitch"
13 | "github.com/unitoftime/glitch/examples/assets"
14 | )
15 |
16 | func main() {
17 | glitch.Run(run)
18 | }
19 |
20 | func run() {
21 | win, err := glitch.NewWindow(1920, 1080, "Glitch - PixelArt", glitch.WindowConfig{
22 | Vsync: true,
23 | })
24 | if err != nil {
25 | panic(err)
26 | }
27 |
28 | // // shader, err := glitch.NewShader(shaders.PixelArtShader)
29 | // shader, err := glitch.NewShader(shaders.SpriteShader)
30 | // if err != nil {
31 | // panic(err)
32 | // }
33 | // pass := glitch.NewRenderPass(shader)
34 |
35 | zoom := 1.0
36 |
37 | img, err := assets.LoadImage("gopher-small.png")
38 | if err != nil {
39 | panic(err)
40 | }
41 | texture := glitch.NewTexture(img, false)
42 | sprite := glitch.NewSprite(texture, texture.Bounds())
43 |
44 | targetBounds := win.Bounds()
45 |
46 | length := 10
47 | man := make([]Man, length)
48 | for i := range man {
49 | man[i] = NewMan(targetBounds.Center())
50 | }
51 |
52 | // w := sprite.Bounds().W()
53 | // h := sprite.Bounds().H()
54 |
55 | // Text
56 | atlas, err := glitch.DefaultAtlas()
57 | if err != nil {
58 | panic(err)
59 | }
60 |
61 | text := atlas.Text("", 1)
62 |
63 | min := time.Duration(0)
64 | max := time.Duration(0)
65 |
66 | counter := 0
67 | camera := glitch.NewCameraOrtho()
68 | camera.DepthRange = glitch.Vec2{-127, 127}
69 |
70 | start := time.Now()
71 | var dt time.Duration
72 |
73 | mat := glitch.Mat4Ident
74 | var t time.Duration
75 | for !win.Closed() {
76 | targetBounds = win.Bounds()
77 |
78 | if win.Pressed(glitch.KeyEscape) {
79 | win.Close()
80 | }
81 |
82 | camera.SetOrtho2D(targetBounds)
83 | camera.SetView2D(0, 0, zoom, zoom)
84 | glitch.SetCamera(camera)
85 |
86 | _, sy := win.MouseScroll()
87 | if sy > 0 {
88 | zoom += 0.1
89 | } else if sy < 0 {
90 | zoom -= 0.1
91 | }
92 |
93 | start = time.Now()
94 | t += dt
95 |
96 | counter = (counter + 1) % 60
97 |
98 | radius := 50.0
99 | man[0].position.X = radius*math.Cos(t.Seconds()) + targetBounds.Center().X
100 | man[0].position.Y = radius*math.Sin(t.Seconds()) + targetBounds.Center().Y
101 |
102 | man[1].position.X = radius*math.Cos(t.Seconds()) + targetBounds.Center().X
103 | man[1].position.Y = (2 * radius) + targetBounds.Center().Y
104 |
105 | man[2].position.X = (2 * radius) + targetBounds.Center().X
106 | man[2].position.Y = radius*math.Sin(t.Seconds()) + targetBounds.Center().Y
107 |
108 | glitch.Clear(win, glitch.White)
109 |
110 | if counter == 0 {
111 | text.Clear()
112 | text.Set(fmt.Sprintf("%2.2f (%2.2f, %2.2f) ms",
113 | 1000*dt.Seconds(),
114 | 1000*min.Seconds(),
115 | 1000*max.Seconds()))
116 | min = 100000000000
117 | max = 0
118 | }
119 | text.DrawColorMask(win, glitch.Mat4Ident, glitch.Black)
120 |
121 | for i := range man {
122 | mat = glitch.Mat4Ident
123 | // mat.Translate(math.Round(man[i].position[0]), math.Round(man[i].position[1]), 0)
124 | mat.Scale(4, 4, 1).Translate(math.Round(man[i].position.X), math.Round(man[i].position.Y), 0)
125 | // mat.Scale(1, 1, 1).Translate(man[i].position[0], man[i].position[1], 0)
126 | sprite.DrawColorMask(win, mat, man[i].color)
127 | }
128 |
129 | // texelsPerPixel := 1.0 / zoom
130 | // pass.SetUniform("texelsPerPixel", float32(texelsPerPixel))
131 |
132 | win.Update()
133 |
134 | dt = time.Since(start)
135 |
136 | // dt = time.Since(start)
137 | if dt > max {
138 | max = dt
139 | }
140 | if dt < min {
141 | min = dt
142 | }
143 | // fmt.Println(dt.Seconds() * 1000)
144 | }
145 | }
146 |
147 | type Man struct {
148 | position, velocity glitch.Vec2
149 | color glitch.RGBA
150 | layer uint8
151 | }
152 |
153 | func NewMan(pos glitch.Vec2) Man {
154 | vScale := 0.1
155 | return Man{
156 | position: pos,
157 | velocity: glitch.Vec2{float64(2 * vScale * (rand.Float64() - 0.5)),
158 | float64(2 * vScale * (rand.Float64() - 0.5))},
159 | color: glitch.White,
160 | }
161 | }
162 |
163 | //--------------------------------------------------------------------------------
164 | // func main() {
165 | // glitch.Run(run)
166 | // }
167 |
168 | // func run() {
169 | // win, err := glitch.NewWindow(1920, 1080, "Glitch - PixelArt", glitch.WindowConfig{
170 | // Vsync: true,
171 | // })
172 | // if err != nil {
173 | // panic(err)
174 | // }
175 |
176 | // pixelShader, err := glitch.NewShader(shaders.PixelArtShader)
177 | // if err != nil {
178 | // panic(err)
179 | // }
180 | // pixelPass := glitch.NewRenderPass(pixelShader)
181 |
182 | // shader, err := glitch.NewShader(shaders.SpriteShader)
183 | // if err != nil {
184 | // panic(err)
185 | // }
186 |
187 | // pass := glitch.NewRenderPass(shader)
188 | // // pass.DepthTest = true
189 | // // pass.SoftwareSort = glitch.SoftwareSortY
190 |
191 | // pixelCam := glitch.NewCameraOrtho()
192 |
193 | // zoom := 1.0
194 |
195 | // upscale := 2.0
196 | // targetBounds := glitch.R(0, 0, 1920, 1080).Scaled(1 / upscale).Snap()
197 | // frame := glitch.NewFrame(targetBounds, false)
198 |
199 | // img, err := assets.LoadImage("gopher-small.png")
200 | // if err != nil {
201 | // panic(err)
202 | // }
203 | // texture := glitch.NewTexture(img, false)
204 | // sprite := glitch.NewSprite(texture, texture.Bounds())
205 | // // sprite.Translucent = true
206 |
207 | // length := 10
208 | // man := make([]Man, length)
209 | // for i := range man {
210 | // man[i] = NewMan(targetBounds.Center())
211 | // }
212 |
213 | // // w := sprite.Bounds().W()
214 | // // h := sprite.Bounds().H()
215 |
216 | // // Text
217 | // atlas, err := glitch.DefaultAtlas()
218 | // if err != nil {
219 | // panic(err)
220 | // }
221 |
222 | // text := atlas.Text("", 1)
223 |
224 | // min := time.Duration(0)
225 | // max := time.Duration(0)
226 |
227 | // counter := 0
228 | // camera := glitch.NewCameraOrtho()
229 | // camera.DepthRange = glitch.Vec2{-127, 127}
230 |
231 | // start := time.Now()
232 | // var dt time.Duration
233 |
234 | // mat := glitch.Mat4Ident
235 | // var t time.Duration
236 | // for !win.Closed() {
237 | // if win.Pressed(glitch.KeyEscape) {
238 | // win.Close()
239 | // }
240 | // _, sy := win.MouseScroll()
241 | // if sy > 0 {
242 | // zoom += 0.1
243 | // } else if sy < 0 {
244 | // zoom -= 0.1
245 | // }
246 |
247 | // start = time.Now()
248 | // t += dt
249 |
250 | // counter = (counter + 1) % 60
251 |
252 | // radius := 50.0
253 | // man[0].position[0] = radius * math.Cos(t.Seconds()) + targetBounds.Center()[0]
254 | // man[0].position[1] = radius * math.Sin(t.Seconds()) + targetBounds.Center()[1]
255 |
256 | // man[1].position[0] = radius * math.Cos(t.Seconds()) + targetBounds.Center()[0]
257 | // man[1].position[1] = (2 * radius) + targetBounds.Center()[1]
258 |
259 | // man[2].position[0] = (2 * radius) + targetBounds.Center()[0]
260 | // man[2].position[1] = radius * math.Sin(t.Seconds()) + targetBounds.Center()[1]
261 |
262 | // // for i := range man {
263 | // // man[i].position[0] += man[i].velocity[0]
264 | // // man[i].position[1] += man[i].velocity[1]
265 |
266 | // // if man[i].position[0] <= 0 || (man[i].position[0]+w) >= float64(1920) {
267 | // // man[i].velocity[0] = -man[i].velocity[0]
268 | // // }
269 | // // if man[i].position[1] <= 0 || (man[i].position[1]+h) >= float64(1080) {
270 | // // man[i].velocity[1] = -man[i].velocity[1]
271 | // // }
272 | // // }
273 |
274 | // pass.Clear()
275 | // pixelPass.Clear()
276 |
277 | // camera.SetOrtho2D(frame.Bounds())
278 | // camera.SetView2D(0, 0, 1, 1)
279 |
280 | // pixelCam.SetOrtho2D(win.Bounds())
281 | // pixelCam.SetView2D(0, 0, zoom, zoom)
282 |
283 | // pass.SetLayer(0)
284 | // if counter == 0 {
285 | // text.Clear()
286 | // text.Set(fmt.Sprintf("%2.2f (%2.2f, %2.2f) ms",
287 | // 1000*dt.Seconds(),
288 | // 1000*min.Seconds(),
289 | // 1000*max.Seconds()))
290 | // min = 100000000000
291 | // max = 0
292 | // }
293 | // text.DrawColorMask(pass, glitch.Mat4Ident, glitch.Black)
294 |
295 | // pass.SetLayer(1)
296 | // for i := range man {
297 | // mat = glitch.Mat4Ident
298 | // // mat.Translate(math.Round(man[i].position[0]), math.Round(man[i].position[1]), 0)
299 | // mat.Translate(man[i].position[0], man[i].position[1], 0)
300 | // sprite.DrawColorMask(pass, mat, man[i].color)
301 | // }
302 |
303 | // glitch.Clear(win, glitch.White)
304 | // glitch.Clear(frame, glitch.White)
305 |
306 | // pass.SetCamera2D(camera)
307 | // pass.Draw(frame)
308 |
309 | // // frame.Draw(pixelPass, glitch.Mat4Ident)
310 | // frame.RectDraw(pixelPass, win.Bounds())
311 |
312 | // pixelPass.SetCamera2D(pixelCam)
313 | // texelsPerPixel := 1.0 / upscale
314 | // pixelPass.SetUniform("texelsPerPixel", float32(texelsPerPixel))
315 | // pixelPass.Draw(win)
316 |
317 | // win.Update()
318 |
319 | // dt = time.Since(start)
320 |
321 | // // dt = time.Since(start)
322 | // if dt > max {
323 | // max = dt
324 | // }
325 | // if dt < min {
326 | // min = dt
327 | // }
328 | // // fmt.Println(dt.Seconds() * 1000)
329 | // }
330 | // }
331 |
332 | // type Man struct {
333 | // position, velocity glitch.Vec2
334 | // color glitch.RGBA
335 | // layer uint8
336 | // }
337 |
338 | // func NewMan(pos glitch.Vec2) Man {
339 | // vScale := 0.1
340 | // return Man{
341 | // position: pos,
342 | // velocity: glitch.Vec2{float64(2 * vScale * (rand.Float64() - 0.5)),
343 | // float64(2 * vScale * (rand.Float64() - 0.5))},
344 | // color: glitch.White,
345 | // }
346 | // }
347 |
--------------------------------------------------------------------------------
/examples/sdf/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | _ "image/png"
6 | "log"
7 | "os"
8 | "runtime"
9 | "runtime/pprof"
10 |
11 | "github.com/unitoftime/flow/glm"
12 | "github.com/unitoftime/glitch"
13 | "github.com/unitoftime/glitch/examples/assets"
14 | )
15 |
16 | var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
17 | var memprofile = flag.String("memprofile", "", "write memory profile to `file`")
18 |
19 | func main() {
20 | flag.Parse()
21 | if *cpuprofile != "" {
22 | f, err := os.Create(*cpuprofile)
23 | if err != nil {
24 | log.Fatal("could not create CPU profile: ", err)
25 | }
26 | defer f.Close() // error handling omitted for example
27 | go func() {
28 | if err := pprof.StartCPUProfile(f); err != nil {
29 | log.Fatal("could not start CPU profile: ", err)
30 | }
31 | }()
32 | defer pprof.StopCPUProfile()
33 | }
34 |
35 | glitch.Run(runGame)
36 |
37 | if *memprofile != "" {
38 | f, err := os.Create(*memprofile)
39 | if err != nil {
40 | log.Fatal("could not create memory profile: ", err)
41 | }
42 | defer f.Close() // error handling omitted for example
43 | runtime.GC() // get up-to-date statistics
44 | if err := pprof.WriteHeapProfile(f); err != nil {
45 | log.Fatal("could not write memory profile: ", err)
46 | }
47 | }
48 | }
49 |
50 | // func main() {
51 | // log.Println("Begin")
52 | // glitch.Run(runGame)
53 | // }
54 |
55 | func runGame() {
56 | win, err := glitch.NewWindow(1920, 1080, "Glitch UI Demo", glitch.WindowConfig{
57 | Vsync: true,
58 | Samples: 0,
59 | })
60 | if err != nil {
61 | panic(err)
62 | }
63 |
64 | atlasImg, err := assets.LoadImage("atlas-msdf.png")
65 | if err != nil {
66 | panic(err)
67 | }
68 | atlasJson := glitch.SdfAtlas{}
69 | err = assets.LoadJson("atlas-msdf.json", &atlasJson)
70 | if err != nil {
71 | panic(err)
72 | }
73 | atlas, err := glitch.AtlasFromSdf(atlasJson, atlasImg, 3)
74 |
75 | atlas2, err := glitch.DefaultAtlas()
76 |
77 | // Text
78 | // atlas, err := glitch.BasicFontAtlas()
79 | if err != nil {
80 | panic(err)
81 | }
82 |
83 | // text := atlas.Text("Hello World", 1.0)
84 | text := atlas.Text("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 1.0)
85 | text2 := atlas2.Text("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 1.0)
86 |
87 | screenScale := 1.0 // This is just a weird scaling number
88 |
89 | // A screenspace camera
90 | camera := glitch.NewCameraOrtho()
91 | camera.SetOrtho2D(win.Bounds())
92 | camera.SetView2D(0, 0, screenScale, screenScale)
93 |
94 | geom := glitch.NewGeomDraw()
95 | mesh := glitch.NewMesh()
96 |
97 | drawText1 := true
98 | drawText2 := false
99 |
100 | // scale := 1.0
101 | for !win.Closed() {
102 | if win.Pressed(glitch.KeyEscape) {
103 | win.Close()
104 | }
105 |
106 | if win.JustPressed(glitch.KeyS) {
107 | drawText1 = !drawText1
108 | }
109 | if win.JustPressed(glitch.KeyD) {
110 | drawText2 = !drawText2
111 | }
112 | // if win.JustPressed(glitch.Key1) {
113 | // scale = 1.0
114 | // }
115 | // if win.JustPressed(glitch.Key2) {
116 | // scale = 2.0
117 | // }
118 | // if win.JustPressed(glitch.Key3) {
119 | // scale = 3.0
120 | // }
121 |
122 | mesh.Clear()
123 |
124 | camera.SetOrtho2D(win.Bounds())
125 | camera.SetView2D(0, 0, screenScale, screenScale)
126 | glitch.SetCamera(camera)
127 |
128 | // mx, my := win.MousePosition()
129 | // log.Println("Mouse: ", mx, my)
130 |
131 | glitch.Clear(win, glm.Greyscale(0.5))
132 |
133 | // mat := glitch.Mat4Ident
134 | // mat.Translate(win.Bounds().Center()[0], win.Bounds().Center()[1], 0)
135 | // text.Draw(pass, mat)
136 |
137 | scale := 0.1
138 | lh := atlas.LineHeight()
139 | y := 0.0
140 | for i := 0; i < 25; i++ {
141 | mat := glitch.Mat4Ident
142 | mat.
143 | Scale(scale, scale, 1).
144 | Translate(10, y+10, 0)
145 |
146 | if drawText1 {
147 | text.Draw(win, mat)
148 | {
149 | geom.SetColor(glitch.RGBA{0, 0, 1, 1})
150 | r := text.Bounds()
151 | r.Min = mat.Apply(r.Min.Vec3()).Vec2()
152 | r.Max = mat.Apply(r.Max.Vec3()).Vec2()
153 | geom.Rectangle2(mesh, r, 1)
154 | }
155 | }
156 |
157 | if drawText2 {
158 | text2.DrawColorMask(win, mat, glitch.RGBA{1, 0, 0, 1})
159 | {
160 | geom.SetColor(glitch.RGBA{0, 1, 0, 1})
161 | r := text2.Bounds()
162 | r.Min = mat.Apply(r.Min.Vec3()).Vec2()
163 | r.Max = mat.Apply(r.Max.Vec3()).Vec2()
164 | geom.Rectangle2(mesh, r, 1)
165 | }
166 | }
167 |
168 | y += lh * scale
169 | scale += 0.5
170 | }
171 |
172 | mesh.Draw(win, glitch.Mat4Ident)
173 |
174 | win.Update()
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/examples/simple/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | _ "image/png"
5 |
6 | "github.com/unitoftime/flow/glm"
7 | "github.com/unitoftime/glitch"
8 | "github.com/unitoftime/glitch/examples/assets"
9 | )
10 |
11 | func check(err error) {
12 | if err != nil {
13 | panic(err)
14 | }
15 | }
16 |
17 | func main() {
18 | glitch.Run(run)
19 | }
20 |
21 | func run() {
22 | win, err := glitch.NewWindow(1920, 1080, "Glitch Demo", glitch.WindowConfig{
23 | Vsync: true,
24 | })
25 | check(err)
26 |
27 | // TODO: Should come from internal default. but could be overridden
28 | // shader, err := glitch.NewShader(shaders.SpriteShader)
29 | // check(err)
30 | // msdfShader, err := glitch.NewShader(shaders.MSDFShader)
31 | // check(err)
32 |
33 | img, err := assets.LoadImage("gopher.png")
34 | check(err)
35 |
36 | texture := glitch.NewTexture(img, false)
37 | sprite := glitch.NewSprite(texture, texture.Bounds())
38 |
39 | frame := glitch.NewFrame(win.Bounds(), true)
40 |
41 | atlasImg, err := assets.LoadImage("atlas-msdf.png")
42 | check(err)
43 | atlasJson := glitch.SdfAtlas{}
44 | err = assets.LoadJson("atlas-msdf.json", &atlasJson)
45 | check(err)
46 | atlas, err := glitch.AtlasFromSdf(atlasJson, atlasImg, 1.0)
47 | check(err)
48 | text := atlas.Text("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 1.0)
49 | text.Material().SetUniform("u_threshold", 0.6) // TODO: Should mostly come from default
50 |
51 | defaultAtlas, err := glitch.DefaultAtlas()
52 | check(err)
53 | defaultText := defaultAtlas.Text("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 1.0)
54 |
55 | screenScale := 1.0 // This is just a weird scaling number
56 |
57 | // A screenspace camera
58 | camera := glitch.NewCameraOrtho()
59 |
60 | sorter := glitch.NewSorter()
61 |
62 | for !win.Closed() {
63 | if win.Pressed(glitch.KeyEscape) {
64 | win.Close()
65 | }
66 |
67 | camera.SetOrtho2D(win.Bounds())
68 | camera.SetView2D(0, 0, screenScale, screenScale)
69 | glitch.SetCamera(camera)
70 |
71 | // sorter.SetCamera(camera)
72 |
73 | // you were working on migrating the batcher to be internal to each individual frame buffer thing. so then you just draw directly to one of those and it gets rendered
74 | // General Plan
75 | // 1. complex mode: draws -> sorter -> batcher -> opengl
76 | // 2. simple mode: draws -> batcher -> opengl
77 | // 3. batcher is internal to the framebuffer we are drawing to
78 | // 4. bufferpools are managed by each shader
79 | // 5. user perspective is that everything is immediate mode, but they can sort their draw commands by wrapping the immediate API with a sorter
80 |
81 | mat := glitch.Mat4Ident
82 |
83 | sprite.Draw(sorter, *mat.Translate(100, 100, 0))
84 | text.Draw(sorter, *mat.Translate(100, 100, 0))
85 |
86 | glitch.Clear(frame, glm.Alpha(0.5))
87 | sprite.Draw(frame, glm.Mat4Ident)
88 |
89 | sorter.Draw(frame)
90 |
91 | glitch.Clear(win, glm.Greyscale(0.5))
92 |
93 | mat = glitch.Mat4Ident
94 | mat.Translate(win.Bounds().Center().X, win.Bounds().Center().Y-100, 0)
95 | defaultText.Draw(win, mat)
96 |
97 | mat = glitch.Mat4Ident
98 | frame.Draw(win, *mat.Translate(100, 100, 0))
99 | sprite.Draw(win, glitch.Mat4Ident)
100 |
101 | // glitch.Clear(frame, glitch.Alpha(0.5))
102 | // shader.Use()
103 | // shader.SetUniform("projection", camera.Projection)
104 | // shader.SetUniform("view", camera.View)
105 | // sprite.Draw(frame, glitch.Mat4Ident)
106 |
107 | // sorter.Draw(frame)
108 |
109 | // glitch.Clear(win, glitch.Greyscale(0.5))
110 | // shader.Use()
111 | // shader.SetUniform("projection", camera.Projection)
112 | // shader.SetUniform("view", camera.View)
113 |
114 | // mat = glitch.Mat4Ident
115 | // mat.Translate(win.Bounds().Center().X, win.Bounds().Center().Y - 100, 0)
116 | // defaultText.Draw(win, mat)
117 |
118 | // msdfShader.Use()
119 | // msdfShader.SetUniform("projection", camera.Projection)
120 | // msdfShader.SetUniform("view", camera.View)
121 | // msdfShader.SetUniform("u_threshold", float32(0.5))
122 |
123 | // mat = glitch.Mat4Ident
124 | // mat.Translate(win.Bounds().Center().X, win.Bounds().Center().Y, 0)
125 | // text.Draw(win, mat)
126 |
127 | // shader.Use()
128 | // shader.SetUniform("projection", camera.Projection)
129 | // shader.SetUniform("view", camera.View)
130 |
131 | // mat = glitch.Mat4Ident
132 | // frame.Draw(win, *mat.Translate(100, 100, 0))
133 | // sprite.Draw(win, glitch.Mat4Ident)
134 |
135 | win.Update()
136 | }
137 | }
138 |
139 | // func check(err error) {
140 | // if err != nil {
141 | // panic(err)
142 | // }
143 | // }
144 |
145 | // func main() {
146 | // glitch.Run(run)
147 | // }
148 |
149 | // func run() {
150 | // win, err := glitch.NewWindow(1920, 1080, "Glitch Demo", glitch.WindowConfig{
151 | // Vsync: true,
152 | // })
153 | // check(err)
154 |
155 | // // shader, err := glitch.NewShader(shaders.SpriteShader)
156 | // // check(err)
157 |
158 | // MSDFShader, err := glitch.NewShader(shaders.MSDFShader)
159 | // check(err)
160 |
161 | // pass := glitch.NewRenderPass(MSDFShader)
162 |
163 | // atlasImg, err := assets.LoadImage("atlas-msdf.png")
164 | // check(err)
165 | // atlasJson := glitch.SdfAtlas{}
166 | // err = assets.LoadJson("atlas-msdf.json", &atlasJson)
167 | // check(err)
168 |
169 | // sdfAtlas, err := glitch.AtlasFromSdf(atlasJson, atlasImg)
170 | // check(err)
171 |
172 | // // Text
173 | // atlas, err := glitch.BasicFontAtlas()
174 | // check(err)
175 |
176 | // // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
177 | // sdfText := sdfAtlas.Text("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 1.0)
178 | // text := atlas.Text("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 1.0)
179 |
180 | // screenScale := 1.0 // This is just a weird scaling number
181 |
182 | // // A screenspace camera
183 | // camera := glitch.NewCameraOrtho()
184 | // camera.SetOrtho2D(win.Bounds())
185 | // camera.SetView2D(0, 0, screenScale, screenScale)
186 |
187 | // for !win.Closed() {
188 | // if win.Pressed(glitch.KeyEscape) {
189 | // win.Close()
190 | // }
191 |
192 | // camera.SetOrtho2D(win.Bounds())
193 | // camera.SetView2D(0, 0, screenScale, screenScale)
194 |
195 | // glitch.Clear(win, glitch.Greyscale(0.5))
196 | // pass.Clear()
197 |
198 | // mat := glitch.Mat4Ident
199 | // mat.
200 | // Scale(4, 4, 1).
201 | // Translate(win.Bounds().Center().X, win.Bounds().Center().Y, 0)
202 | // text.Draw(pass, mat)
203 |
204 | // lh := sdfAtlas.LineHeight()
205 | // y := 0.0
206 | // scale := 0.1
207 | // for i := 0; i < 25; i++ {
208 | // mat := glitch.Mat4Ident
209 | // mat.
210 | // Scale(scale, scale, 1).
211 | // Translate(0, y, 0)
212 | // sdfText.Draw(pass, mat)
213 |
214 | // y += lh * scale
215 | // scale += 0.1
216 | // }
217 |
218 | // pass.SetUniform("u_threshold", float32(0.5));
219 | // pass.SetCamera2D(camera)
220 | // pass.Draw(win)
221 |
222 | // win.Update()
223 | // }
224 | // }
225 |
--------------------------------------------------------------------------------
/examples/web/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | GOOS=js GOARCH=wasm go build -o gophermark.wasm github.com/unitoftime/glitch/examples/gophermark
3 | GOOS=js GOARCH=wasm go build -ldflags "-s" -o frame.wasm github.com/unitoftime/glitch/examples/frame
4 | GOOS=js GOARCH=wasm go build -ldflags "-s" -o ui.wasm github.com/unitoftime/glitch/examples/ui
5 | GOOS=js GOARCH=wasm go build -ldflags "-s" -o 3d.wasm github.com/unitoftime/glitch/examples/3d
6 | GOOS=js GOARCH=wasm go build -ldflags "-s" -o graph.wasm github.com/unitoftime/glitch/examples/graph
7 | GOOS=js GOARCH=wasm go build -ldflags "-s" -o controller.wasm github.com/unitoftime/glitch/examples/controller
8 |
9 |
10 | # tinygo build -o gophermark-tiny.wasm -target wasm github.com/unitoftime/glitch/examples/gophermark
11 |
12 | run: all
13 | go run main.go
14 |
--------------------------------------------------------------------------------
/examples/web/assets/css/main.css:
--------------------------------------------------------------------------------
1 | :root {
2 | box-sizing: border-box;
3 | }
4 |
5 | html, body {
6 | margin: 0;
7 | padding: 0;
8 | }
9 |
10 | .content {
11 | position: relative;
12 | width: 100vw;
13 | height: 100vh;
14 | }
15 |
16 |
17 | #gocanvas {
18 | position: fixed;
19 | opacity: 0.9;
20 | width: 100%;
21 | height: 100%;
22 | top: 0;
23 | right: 0;
24 | bottom: 0;
25 | left: 0;
26 | }
27 |
28 | /* Dropdown Button */
29 | .dropbtn {
30 | background-color: #04aa6d;
31 | color: white;
32 | padding: 16px;
33 | font-size: 16px;
34 | border: none;
35 | }
36 |
37 | /* The container
- needed to position the dropdown content */
38 | .dropdown {
39 | position: absolute;
40 | display: inline-block;
41 | margin: 1em;
42 | }
43 |
44 | /* Dropdown Content (Hidden by Default) */
45 | .dropdown-content {
46 | display: none;
47 | position: absolute;
48 | background-color: #f1f1f1;
49 | min-width: 160px;
50 | box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
51 | z-index: 1;
52 | }
53 |
54 | /* Links inside the dropdown */
55 | .dropdown-content a {
56 | color: black;
57 | padding: 12px 16px;
58 | text-decoration: none;
59 | display: block;
60 | }
61 |
62 | /* Change color of dropdown links on hover */
63 | .dropdown-content a:hover {
64 | background-color: #ddd;
65 | }
66 |
67 | /* Show the dropdown menu on hover */
68 | .dropdown:hover .dropdown-content {
69 | display: block;
70 | }
71 |
72 | /* Change the background color of the dropdown button when the dropdown content is shown */
73 | .dropdown:hover .dropbtn {
74 | background-color: #3e8e41;
75 | }
76 |
--------------------------------------------------------------------------------
/examples/web/assets/scripts/main.js:
--------------------------------------------------------------------------------
1 | if (!WebAssembly.instantiateStreaming) {
2 | // polyfill
3 | WebAssembly.instantiateStreaming = (source, importObject) => {
4 | const instantiate = (buffer) =>
5 | WebAssembly.instantiate(buffer, importObject);
6 |
7 | if (source instanceof Response) {
8 | return source.arrayBuffer().then(instantiate);
9 | }
10 |
11 | return source.then((res) => res.arrayBuffer()).then(instantiate);
12 | };
13 | }
14 |
15 | const go = new Go();
16 | const params = new URLSearchParams(location.search);
17 | const exampleName = params.get("name") || "gophermark.wasm";
18 |
19 | WebAssembly.instantiateStreaming(fetch(exampleName), go.importObject)
20 | .then(async ({ module, instance }) => {
21 | console.clear();
22 |
23 | try {
24 | await go.run(instance);
25 | // Note: Don't know why we need to reset the instance here. We don't seem use it again.
26 | instance = await WebAssembly.instantiate(module, go.importObject);
27 | // Note: Logging the result here seems wrong.
28 | console.log("Ran WASM:", result);
29 | } catch (error) {
30 | console.log("Failed to run WASM:", error);
31 | }
32 | })
33 | .catch((error) => {
34 | console.log("Could not create wasm instance", error);
35 | });
36 |
--------------------------------------------------------------------------------
/examples/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Glitch Web Demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/web/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | )
7 |
8 | func main() {
9 | log.Println("Serving on 8081")
10 | if err := http.ListenAndServe(`:8081`, http.FileServer(http.Dir(`.`))); err != nil {
11 | panic(err)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/frame.go:
--------------------------------------------------------------------------------
1 | package glitch
2 |
3 | import (
4 | "runtime"
5 |
6 | "github.com/unitoftime/flow/glm"
7 | "github.com/unitoftime/glitch/internal/gl"
8 | "github.com/unitoftime/glitch/internal/mainthread"
9 | )
10 |
11 | type Frame struct {
12 | fbo gl.Framebuffer
13 | tex *Texture
14 | depth gl.Texture
15 | mesh *Mesh
16 | material Material
17 | bounds Rect
18 | }
19 |
20 | // Type? Color, depth, stencil?
21 | func NewFrame(bounds Rect, smooth bool) *Frame {
22 | var frame = &Frame{
23 | bounds: bounds,
24 | }
25 |
26 | // Create texture
27 | frame.tex = NewEmptyTexture(int(bounds.W()), int(bounds.H()), smooth)
28 |
29 | // Create mesh (in case we want to draw the fbo to another target)
30 | frame.mesh = NewQuadMesh(bounds, glm.R(0, 1, 1, 0))
31 | frame.material = NewMaterial(GetDefaultSpriteShader())
32 | frame.material.texture = frame.tex
33 |
34 | mainthread.Call(func() {
35 | frame.fbo = gl.CreateFramebuffer()
36 | gl.BindFramebuffer(gl.FRAMEBUFFER, frame.fbo)
37 | gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, frame.tex.texture, 0)
38 |
39 | // https://webgl2fundamentals.org/webgl/lessons/webgl-render-to-texture.html
40 | // TODO - maybe centralize this into texture creation api
41 | // TODO - make fbo depth attachment optional
42 | frame.depth = gl.CreateTexture()
43 | gl.BindTexture(gl.TEXTURE_2D, frame.depth)
44 | // gl.TexImage2DFull(gl.TEXTURE_2D, 0, gl.DEPTH24_STENCIL8, frame.tex.width, frame.tex.height, gl.DEPTH_STENCIL, gl.UNSIGNED_INT_24_8, nil)
45 | gl.TexImage2DFull(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT24, frame.tex.width, frame.tex.height, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, nil)
46 | gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, frame.depth, 0)
47 | })
48 |
49 | runtime.SetFinalizer(frame, (*Frame).delete)
50 | return frame
51 | }
52 |
53 | func (f *Frame) Bounds() Rect {
54 | return f.bounds
55 | }
56 |
57 | func (f *Frame) Texture() *Texture {
58 | return f.tex
59 | }
60 |
61 | func (f *Frame) Draw(target BatchTarget, matrix Mat4) {
62 | f.DrawColorMask(target, matrix, RGBA{1.0, 1.0, 1.0, 1.0})
63 | }
64 | func (f *Frame) DrawColorMask(target BatchTarget, matrix Mat4, mask RGBA) {
65 | // pass.SetTexture(0, s.texture)
66 | target.Add(f.mesh.g(), glm4(matrix), mask, f.material)
67 | }
68 |
69 | func (f *Frame) RectDraw(target BatchTarget, bounds Rect) {
70 | f.RectDrawColorMask(target, bounds, White)
71 | }
72 |
73 | func (f *Frame) RectDrawColorMask(target BatchTarget, bounds Rect, mask RGBA) {
74 | matrix := Mat4Ident
75 | matrix.Scale(bounds.W()/f.bounds.W(), bounds.H()/f.bounds.H(), 1).
76 | Translate(bounds.Min.X, bounds.Min.Y, 0)
77 | // Note: because frames are anchored to the bottom left, we don't have to shift by center
78 | // .Translate(bounds.W()/2 + bounds.Min[0], bounds.H()/2 + bounds.Min[1], 0)
79 | f.DrawColorMask(target, matrix, mask)
80 | }
81 |
82 | func (f *Frame) delete() {
83 | mainthread.CallNonBlock(func() {
84 | gl.DeleteFramebuffer(f.fbo)
85 | })
86 | }
87 |
88 | func (f *Frame) Bind() {
89 | state.bindFramebuffer(f.fbo, f.bounds)
90 | }
91 |
92 | func (f *Frame) Material() *Material {
93 | return &f.material
94 | }
95 |
96 | func (f *Frame) Add(filler GeometryFiller, mat glMat4, mask RGBA, material Material) {
97 | setTarget(f)
98 | global.Add(filler, mat, mask, material)
99 | }
100 |
--------------------------------------------------------------------------------
/gamepad.go:
--------------------------------------------------------------------------------
1 | package glitch
2 |
3 | import (
4 | "github.com/unitoftime/glitch/internal/glfw"
5 | "github.com/unitoftime/glitch/internal/mainthread"
6 | )
7 |
8 | type Gamepad int
9 |
10 | const GamepadNone = Gamepad(-1)
11 |
12 | // TODO: Comment out and add function that says: GetAllActiveGamepads() or something
13 | // Note: I don't think that users should be statically referring to gamepads. I can't think of a use for that
14 | // const (
15 | // Gamepad1 = Gamepad(glfw.Joystick1)
16 | // Gamepad2 = Gamepad(glfw.Joystick2)
17 | // Gamepad3 = Gamepad(glfw.Joystick3)
18 | // Gamepad4 = Gamepad(glfw.Joystick4)
19 | // Gamepad5 = Gamepad(glfw.Joystick5)
20 | // Gamepad6 = Gamepad(glfw.Joystick6)
21 | // Gamepad7 = Gamepad(glfw.Joystick7)
22 | // Gamepad8 = Gamepad(glfw.Joystick8)
23 | // Gamepad9 = Gamepad(glfw.Joystick9)
24 | // Gamepad10 = Gamepad(glfw.Joystick10)
25 | // Gamepad11 = Gamepad(glfw.Joystick11)
26 | // Gamepad12 = Gamepad(glfw.Joystick12)
27 | // Gamepad13 = Gamepad(glfw.Joystick13)
28 | // Gamepad14 = Gamepad(glfw.Joystick14)
29 | // Gamepad15 = Gamepad(glfw.Joystick15)
30 | // Gamepad16 = Gamepad(glfw.Joystick16)
31 |
32 | // GamepadLast = Gamepad(glfw.JoystickLast)
33 | // )
34 |
35 | type GamepadAxis int
36 |
37 | const (
38 | AxisLeftX = GamepadAxis(glfw.AxisLeftX)
39 | AxisLeftY = GamepadAxis(glfw.AxisLeftY)
40 | AxisRightX = GamepadAxis(glfw.AxisRightX)
41 | AxisRightY = GamepadAxis(glfw.AxisRightY)
42 | AxisLeftTrigger = GamepadAxis(glfw.AxisLeftTrigger)
43 | AxisRightTrigger = GamepadAxis(glfw.AxisRightTrigger)
44 | AxisLast = GamepadAxis(glfw.AxisLast)
45 | )
46 |
47 | type GamepadButton int
48 |
49 | const (
50 | ButtonA = GamepadButton(glfw.ButtonA)
51 | ButtonB = GamepadButton(glfw.ButtonB)
52 | ButtonX = GamepadButton(glfw.ButtonX)
53 | ButtonY = GamepadButton(glfw.ButtonY)
54 | ButtonLeftBumper = GamepadButton(glfw.ButtonLeftBumper)
55 | ButtonRightBumper = GamepadButton(glfw.ButtonRightBumper)
56 | ButtonBack = GamepadButton(glfw.ButtonBack)
57 | ButtonStart = GamepadButton(glfw.ButtonStart)
58 | ButtonGuide = GamepadButton(glfw.ButtonGuide)
59 | ButtonLeftThumb = GamepadButton(glfw.ButtonLeftThumb)
60 | ButtonRightThumb = GamepadButton(glfw.ButtonRightThumb)
61 | ButtonDpadUp = GamepadButton(glfw.ButtonDpadUp)
62 | ButtonDpadRight = GamepadButton(glfw.ButtonDpadRight)
63 | ButtonDpadDown = GamepadButton(glfw.ButtonDpadDown)
64 | ButtonDpadLeft = GamepadButton(glfw.ButtonDpadLeft)
65 | ButtonCross = GamepadButton(glfw.ButtonCross)
66 | ButtonCircle = GamepadButton(glfw.ButtonCircle)
67 | ButtonSquare = GamepadButton(glfw.ButtonSquare)
68 | ButtonTriangle = GamepadButton(glfw.ButtonTriangle)
69 |
70 | ButtonFirst = ButtonA
71 | ButtonLast = GamepadButton(glfw.ButtonLast)
72 | )
73 |
74 | // Returns the primary gamepad
75 | func (w *Window) GetPrimaryGamepad() Gamepad {
76 | return w.currentPrimaryGamepad
77 | }
78 |
79 | // Returns true if the gamepad state is considered active this frame
80 | func checkGamepadActive(state *glfw.GamepadState) bool {
81 | if state == nil {
82 | return false
83 | }
84 |
85 | for i := range state.Buttons {
86 | if state.Buttons[i] == glfw.Press {
87 | return true
88 | }
89 | }
90 | return false
91 | }
92 |
93 | func (w *Window) getGamepadState(g Gamepad) *glfw.GamepadState {
94 | if g == GamepadNone {
95 | return nil
96 | }
97 |
98 | mainthread.Call(func() {
99 | ptr := w.cachedGamepadStates[g]
100 | if ptr == nil {
101 | ptr = glfw.Joystick(g).GetGamepadState()
102 | } else {
103 | newPtr := glfw.Joystick(g).GetGamepadState()
104 | if newPtr != nil {
105 | *ptr = *newPtr
106 | }
107 | }
108 | w.cachedGamepadStates[g] = ptr
109 |
110 | // ret = glfw.Joystick(g).GetGamepadState()
111 | })
112 | ptr := w.cachedGamepadStates[g]
113 | return ptr
114 | // return ret
115 | }
116 |
117 | // Returns true if the gamepad button is pressed, else returns false
118 | func (w *Window) GetGamepadPressed(g Gamepad, button GamepadButton) bool {
119 | if g == GamepadNone {
120 | return false
121 | }
122 |
123 | return w.pressedGamepad[button]
124 | }
125 |
126 | // Returns true if the gamepad button was just pressed this frame, else returns false
127 | func (w *Window) GetGamepadJustPressed(g Gamepad, button GamepadButton) bool {
128 | if g == GamepadNone {
129 | return false
130 | }
131 |
132 | return w.justPressedGamepad[button]
133 | }
134 |
135 | // Returns the gamepad axis value, ranging on -1 to +1
136 | func (w *Window) GetGamepadAxis(g Gamepad, axis GamepadAxis) float64 {
137 | if g == GamepadNone {
138 | return 0
139 | }
140 |
141 | return w.gamepadAxis[axis]
142 | }
143 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/unitoftime/glitch
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.1
6 |
7 | require (
8 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71
9 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728
10 | github.com/go-gl/mathgl v1.2.0
11 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
12 | github.com/inkyblackness/imgui-go/v4 v4.7.0
13 | github.com/unitoftime/flow v0.0.0-20250410221220-01caf9fce896
14 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
15 | golang.org/x/image v0.26.0
16 | )
17 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
4 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
5 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8=
6 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc=
7 | github.com/go-gl/mathgl v1.2.0 h1:v2eOj/y1B2afDxF6URV1qCYmo1KW08lAMtTbOn3KXCY=
8 | github.com/go-gl/mathgl v1.2.0/go.mod h1:pf9+b5J3LFP7iZ4XXaVzZrCle0Q/vNpB/vDe5+3ulRE=
9 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
10 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
11 | github.com/inkyblackness/imgui-go/v4 v4.7.0 h1:Gc169uXvSydsr/gjw3p1cmHCI1XIpqX7I3KBmfeMMOo=
12 | github.com/inkyblackness/imgui-go/v4 v4.7.0/go.mod h1:g8SAGtOYUP7rYaOB2AsVKCEHmPMDmJKgt4z6d+flhb0=
13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
16 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
17 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
18 | github.com/unitoftime/flow v0.0.0-20250410221220-01caf9fce896 h1:2PRSSiBEslr6Dy/NVrS2Zq2sTw9fw8kp6IpAGezLtmM=
19 | github.com/unitoftime/flow v0.0.0-20250410221220-01caf9fce896/go.mod h1:GYiBR3ctd6ubd5YTtPD1KfnNrR2iQSEOj2m4RbqxCTg=
20 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
21 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
22 | golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
23 | golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
24 |
--------------------------------------------------------------------------------
/graph/graph.go:
--------------------------------------------------------------------------------
1 | package graph
2 |
3 | import (
4 | // "fmt"
5 | "math"
6 |
7 | "github.com/unitoftime/flow/glm"
8 | "github.com/unitoftime/glitch"
9 | )
10 |
11 | // 2D Graph
12 | type Graph struct {
13 | geom *glitch.GeomDraw
14 | mesh *glitch.Mesh
15 | points []glitch.Vec3
16 | bounds glitch.Rect
17 | axes glitch.Rect
18 | }
19 |
20 | func NewGraph(bounds glitch.Rect) *Graph {
21 | g := &Graph{
22 | geom: glitch.NewGeomDraw(),
23 | mesh: glitch.NewMesh(),
24 | points: make([]glitch.Vec3, 0),
25 | bounds: bounds,
26 | }
27 | return g
28 | }
29 |
30 | func (g *Graph) Clear() {
31 | g.mesh.Clear()
32 | }
33 |
34 | func (g *Graph) Bounds() glm.Rect {
35 | return g.bounds
36 | }
37 |
38 | func (g *Graph) SetBounds(bounds glitch.Rect) {
39 | g.bounds = bounds
40 | }
41 |
42 | func (g *Graph) DrawColorMask(target glitch.BatchTarget, matrix glitch.Mat4, mask glitch.RGBA) {
43 | g.mesh.DrawColorMask(target, matrix, mask)
44 | // pass.Add(g.mesh, matrix, mask, glitch.DefaultMaterial(), false)
45 | }
46 |
47 | func (g *Graph) RectDrawColorMask(target glitch.BatchTarget, rect glitch.Rect, mask glitch.RGBA) {
48 | matrix := g.bounds.RectDraw(rect)
49 | g.mesh.DrawColorMask(target, matrix, mask)
50 | }
51 |
52 | func (g *Graph) RectDraw(target glitch.BatchTarget, rect glitch.Rect) {
53 | g.RectDrawColorMask(target, rect, glitch.White)
54 | }
55 |
56 | // TODO - Assumes sorted?
57 | func (g *Graph) Line(series []glitch.Vec2) {
58 | minDomain := math.MaxFloat64
59 | maxDomain := -math.MaxFloat64
60 | minRange := math.MaxFloat64
61 | maxRange := -math.MaxFloat64
62 | for _, p := range series {
63 | minDomain = math.Min(minDomain, float64(p.X))
64 | maxDomain = math.Max(maxDomain, float64(p.X))
65 |
66 | minRange = math.Min(minRange, float64(p.Y))
67 | maxRange = math.Max(maxRange, float64(p.Y))
68 | }
69 |
70 | g.axes = glm.R(minDomain, minRange, maxDomain, maxRange)
71 |
72 | dx := g.bounds.W() / (maxDomain - minDomain)
73 | dy := g.bounds.H() / (maxRange - minRange)
74 | // fmt.Println(dx, dy, rect.H(), maxDomain, minDomain, minRange, maxRange)
75 | g.points = g.points[:0]
76 | for _, p := range series {
77 | g.points = append(g.points, glitch.Vec3{
78 | g.bounds.Min.X + (p.X-(minDomain))*dx,
79 | g.bounds.Min.Y + (p.Y-(minRange))*dy,
80 | 0,
81 | })
82 | }
83 |
84 | g.geom.LineStrip(g.mesh, g.points, 1)
85 | }
86 |
87 | func (g *Graph) Axes() {
88 | g.geom.LineStrip(g.mesh,
89 | []glitch.Vec3{
90 | glitch.Vec3{g.bounds.Min.X, g.bounds.Max.Y, 0},
91 | glitch.Vec3{g.bounds.Min.X, g.bounds.Min.Y, 0},
92 | glitch.Vec3{g.bounds.Max.X, g.bounds.Min.Y, 0},
93 | },
94 | 2,
95 | )
96 |
97 | // g.mesh.Append(g.geom.LineStrip(
98 | // []glitch.Vec3{
99 | // glitch.Vec3{g.bounds.Min.X, g.bounds.Max.Y, 0},
100 | // glitch.Vec3{g.bounds.Min.X, g.bounds.Min.Y, 0},
101 | // glitch.Vec3{g.bounds.Max.X, g.bounds.Min.Y, 0},
102 | // },
103 | // 2,
104 | // ))
105 | }
106 |
107 | func (g *Graph) GetAxes() glitch.Rect {
108 | return g.axes
109 | }
110 |
--------------------------------------------------------------------------------
/internal/gl/README.md:
--------------------------------------------------------------------------------
1 | This is an internal library adapted from: `github.com/goxjs/gl` and modified to my needs.
2 |
3 | ### Other notes:
4 | This is a fork of golang.org/x/mobile/gl package with [CL 8793](https://go-review.googlesource.com/8793)
5 | merged in and Windows support added. This package is fully functional, but may eventually become superceded by
6 | the new x/mobile/gl plan. It will exist and be fully supported until it can be safely replaced by a better package.
7 |
8 | Original License:
9 | ```
10 | Copyright (c) 2009 The Go Authors. All rights reserved.
11 |
12 | Redistribution and use in source and binary forms, with or without
13 | modification, are permitted provided that the following conditions are
14 | met:
15 |
16 | * Redistributions of source code must retain the above copyright
17 | notice, this list of conditions and the following disclaimer.
18 | * Redistributions in binary form must reproduce the above
19 | copyright notice, this list of conditions and the following disclaimer
20 | in the documentation and/or other materials provided with the
21 | distribution.
22 | * Neither the name of Google Inc. nor the names of its
23 | contributors may be used to endorse or promote products derived from
24 | this software without specific prior written permission.
25 |
26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 | ```
38 |
--------------------------------------------------------------------------------
/internal/gl/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | /*
6 | Package gl is a Go cross-platform binding for OpenGL, with an OpenGL ES 2-like API.
7 |
8 | It supports:
9 |
10 | - macOS, Linux and Windows via OpenGL 2.1 backend,
11 |
12 | - iOS and Android via OpenGL ES 2.0 backend,
13 |
14 | - Modern Browsers (desktop and mobile) via WebGL 1.0 backend.
15 |
16 | This is a fork of golang.org/x/mobile/gl package with [CL 8793](https://go-review.googlesource.com/8793)
17 | merged in and Windows support added. This package is fully functional, but may eventually become superceded by
18 | the new x/mobile/gl plan. It will exist and be fully supported until it can be safely replaced by a better package.
19 |
20 | # Usage
21 |
22 | This OpenGL binding has a ContextWatcher, which implements [glfw.ContextWatcher](https://godoc.org/github.com/goxjs/glfw#ContextWatcher)
23 | interface. Recommended usage is with github.com/goxjs/glfw package, which accepts a ContextWatcher in its Init, and takes on the responsibility
24 | of notifying it when context is made current or detached.
25 |
26 | if err := glfw.Init(gl.ContextWatcher); err != nil {
27 | // Handle error.
28 | }
29 | defer glfw.Terminate()
30 |
31 | If you're not using a ContextWatcher-aware glfw library, you must call methods of gl.ContextWatcher yourself whenever
32 | you make a context current or detached.
33 |
34 | window.MakeContextCurrent()
35 | gl.ContextWatcher.OnMakeCurrent(nil)
36 |
37 | glfw.DetachCurrentContext()
38 | gl.ContextWatcher.OnDetach()
39 | */
40 | package gl
41 |
--------------------------------------------------------------------------------
/internal/gl/types.go:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | //go:build !wasm
6 | // +build !wasm
7 |
8 | package gl
9 |
10 | func (v Attrib) Valid() bool { return v != NoAttrib }
11 | func (v Program) Valid() bool { return v != NoProgram }
12 | func (v Shader) Valid() bool { return v != NoShader }
13 | func (v Buffer) Valid() bool { return v != NoBuffer }
14 | func (v Framebuffer) Valid() bool { return v != NoFramebuffer }
15 | func (v Renderbuffer) Valid() bool { return v != NoRenderbuffer }
16 | func (v Texture) Valid() bool { return v != NoTexture }
17 | func (v Uniform) Valid() bool { return v != NoUniform }
18 |
--------------------------------------------------------------------------------
/internal/gl/types_opengl.go:
--------------------------------------------------------------------------------
1 | //go:build !js
2 | // +build !js
3 |
4 | package gl
5 |
6 | // Enum is equivalent to GLenum, and is normally used with one of the
7 | // constants defined in this package.
8 | type Enum uint32
9 |
10 | // Attrib identifies the location of a specific attribute variable.
11 | type Attrib struct {
12 | Value int
13 | }
14 |
15 | // Program identifies a compiled shader program.
16 | type Program struct {
17 | Value uint32
18 | }
19 |
20 | // Shader identifies a GLSL shader.
21 | type Shader struct {
22 | Value uint32
23 | }
24 |
25 | // Buffer identifies a GL buffer object.
26 | type Buffer struct {
27 | Value uint32
28 | }
29 |
30 | // Framebuffer identifies a GL framebuffer.
31 | type Framebuffer struct {
32 | Value uint32
33 | }
34 |
35 | func (o Framebuffer) Equal(o2 Framebuffer) bool {
36 | return o.Value == o2.Value
37 | }
38 |
39 | // A Renderbuffer is a GL object that holds an image in an internal format.
40 | type Renderbuffer struct {
41 | Value uint32
42 | }
43 |
44 | // A Texture identifies a GL texture unit.
45 | type Texture struct {
46 | Value uint32
47 | }
48 |
49 | // Uniform identifies the location of a specific uniform variable.
50 | type Uniform struct {
51 | Value int32
52 | }
53 |
54 | type Object struct {
55 | Value uint32
56 | }
57 |
58 | func (o Object) Equal(o2 Object) bool {
59 | return o.Value == o2.Value
60 | }
61 |
62 | var NoObject = Object{0}
63 |
64 | var NoAttrib = Attrib{0}
65 | var NoProgram = Program{0}
66 | var NoShader = Shader{0}
67 | var NoBuffer = Buffer{0}
68 | var NoFramebuffer = Framebuffer{0}
69 | var NoRenderbuffer = Renderbuffer{0}
70 | var NoTexture = Texture{0}
71 | var NoUniform = Uniform{0}
72 |
--------------------------------------------------------------------------------
/internal/gl/types_opengles.go:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | //go:build linux && (arm || arm64)
6 | // +build linux
7 | // +build arm arm64
8 |
9 | package gl
10 |
11 | /*
12 | #cgo darwin LDFLAGS: -framework OpenGLES
13 | #cgo linux LDFLAGS: -lGLESv2
14 |
15 | #cgo darwin CFLAGS: -Dos_darwin_arm
16 | #cgo linux CFLAGS: -Dos_linux_arm
17 |
18 | #ifdef os_darwin_arm
19 | #include
20 | #endif
21 | #ifdef os_linux_arm
22 | #include
23 | #endif
24 |
25 | void blendColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { glBlendColor(r, g, b, a); }
26 | void clearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { glClearColor(r, g, b, a); }
27 | void clearDepthf(GLfloat d) { glClearDepthf(d); }
28 | void depthRangef(GLfloat n, GLfloat f) { glDepthRangef(n, f); }
29 | void sampleCoverage(GLfloat v, GLboolean invert) { glSampleCoverage(v, invert); }
30 | */
31 | import "C"
32 |
33 | type Enum uint32
34 |
35 | type Attrib struct {
36 | Value uint
37 | }
38 |
39 | type Program struct {
40 | Value uint32
41 | }
42 |
43 | type Shader struct {
44 | Value uint32
45 | }
46 |
47 | type Buffer struct {
48 | Value uint32
49 | }
50 |
51 | type Framebuffer struct {
52 | Value uint32
53 | }
54 |
55 | type Renderbuffer struct {
56 | Value uint32
57 | }
58 |
59 | type Texture struct {
60 | Value uint32
61 | }
62 |
63 | type Uniform struct {
64 | Value int32
65 | }
66 |
67 | var NoAttrib = Attrib{0}
68 | var NoProgram = Program{0}
69 | var NoShader = Shader{0}
70 | var NoBuffer = Buffer{0}
71 | var NoFramebuffer = Framebuffer{0}
72 | var NoRenderbuffer = Renderbuffer{0}
73 | var NoTexture = Texture{0}
74 | var NoUniform = Uniform{0}
75 |
76 | func (v Attrib) c() C.GLuint { return C.GLuint(v.Value) }
77 | func (v Enum) c() C.GLenum { return C.GLenum(v) }
78 | func (v Program) c() C.GLuint { return C.GLuint(v.Value) }
79 | func (v Shader) c() C.GLuint { return C.GLuint(v.Value) }
80 | func (v Buffer) c() C.GLuint { return C.GLuint(v.Value) }
81 | func (v Framebuffer) c() C.GLuint { return C.GLuint(v.Value) }
82 | func (v Renderbuffer) c() C.GLuint { return C.GLuint(v.Value) }
83 | func (v Texture) c() C.GLuint { return C.GLuint(v.Value) }
84 | func (v Uniform) c() C.GLint { return C.GLint(v.Value) }
85 |
86 | func glBoolean(b bool) C.GLboolean {
87 | if b {
88 | return TRUE
89 | }
90 | return FALSE
91 | }
92 |
93 | // Desktop OpenGL and the ES 2/3 APIs have a very slight difference
94 | // that is imperceptible to C programmers: some function parameters
95 | // use the type Glclampf and some use GLfloat. These two types are
96 | // equivalent in size and bit layout (both are single-precision
97 | // floats), but it plays havoc with cgo. We adjust the types by using
98 | // C wrappers for the problematic functions.
99 |
100 | func blendColor(r, g, b, a float32) {
101 | C.blendColor(C.GLfloat(r), C.GLfloat(g), C.GLfloat(b), C.GLfloat(a))
102 | }
103 | func clearColor(r, g, b, a float32) {
104 | C.clearColor(C.GLfloat(r), C.GLfloat(g), C.GLfloat(b), C.GLfloat(a))
105 | }
106 | func clearDepthf(d float32) { C.clearDepthf(C.GLfloat(d)) }
107 | func depthRangef(n, f float32) { C.depthRangef(C.GLfloat(n), C.GLfloat(f)) }
108 | func sampleCoverage(v float32, i bool) { C.sampleCoverage(C.GLfloat(v), glBoolean(i)) }
109 |
--------------------------------------------------------------------------------
/internal/gl/types_webgl_wasm.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | //go:build js && wasm
6 | // +build js,wasm
7 |
8 | package gl
9 |
10 | import "syscall/js"
11 |
12 | type Enum int
13 |
14 | type Attrib struct {
15 | Value int
16 | }
17 |
18 | type Program struct {
19 | js.Value
20 | }
21 |
22 | type Shader struct {
23 | js.Value
24 | }
25 |
26 | type Buffer struct {
27 | js.Value
28 | }
29 |
30 | type Framebuffer struct {
31 | js.Value
32 | }
33 |
34 | func (o Framebuffer) Equal(o2 Framebuffer) bool {
35 | return o.Value.Equal(o2.Value)
36 | }
37 |
38 | type Renderbuffer struct {
39 | js.Value
40 | }
41 |
42 | type Texture struct {
43 | js.Value
44 | }
45 |
46 | type Uniform struct {
47 | js.Value
48 | }
49 |
50 | type Object struct {
51 | js.Value
52 | }
53 |
54 | func (o Object) Equal(o2 Object) bool {
55 | return o.Value.Equal(o2.Value)
56 | }
57 |
58 | var NoObject = Object{js.Null()}
59 |
60 | var NoAttrib = Attrib{0}
61 | var NoProgram = Program{js.Null()}
62 | var NoShader = Shader{js.Null()}
63 | var NoBuffer = Buffer{js.Null()}
64 | var NoFramebuffer = Framebuffer{js.Null()}
65 | var NoRenderbuffer = Renderbuffer{js.Null()}
66 | var NoTexture = Texture{js.Null()}
67 | var NoUniform = Uniform{js.Null()}
68 |
69 | func (v Attrib) Valid() bool { return v != NoAttrib }
70 | func (v Program) Valid() bool { return !v.Equal(NoProgram.Value) }
71 | func (v Shader) Valid() bool { return !v.Equal(NoShader.Value) }
72 | func (v Buffer) Valid() bool { return !v.Equal(NoBuffer.Value) }
73 | func (v Framebuffer) Valid() bool { return !v.Equal(NoFramebuffer) }
74 | func (v Renderbuffer) Valid() bool { return !v.Equal(NoRenderbuffer.Value) }
75 | func (v Texture) Valid() bool { return !v.Equal(NoTexture.Value) }
76 | func (v Uniform) Valid() bool { return !v.Equal(NoUniform.Value) }
77 |
--------------------------------------------------------------------------------
/internal/glfw/.gitignore:
--------------------------------------------------------------------------------
1 | *.wasm
2 | *.js*
3 |
4 | !/test/wasm/*.js
--------------------------------------------------------------------------------
/internal/glfw/README.md:
--------------------------------------------------------------------------------
1 | This is an internal library adapted from: `github.com/goxjs/glfw` and modified to my needs.
2 |
3 | ### Other notes:
4 |
5 | Original License: [MIT License](https://opensource.org/licenses/mit-license.php)
6 |
--------------------------------------------------------------------------------
/internal/glfw/context_webgl_wasm.go:
--------------------------------------------------------------------------------
1 | //go:build js && wasm
2 | // +build js,wasm
3 |
4 | package glfw
5 |
6 | import (
7 | "fmt"
8 | "syscall/js"
9 | )
10 |
11 | type webglSupport struct {
12 | WebGLRenderingContext bool // WebGL1
13 | WebGLRenderingContext2 bool // WebGl2
14 | }
15 |
16 | func (s webglSupport) String() string {
17 | return fmt.Sprintf(
18 | "WebGLRenderingContext: %v | WebGLRenderingContext2: %v",
19 | s.WebGLRenderingContext, s.WebGLRenderingContext2,
20 | )
21 | }
22 |
23 | func getWebglSupport() webglSupport {
24 | support := webglSupport{}
25 |
26 | {
27 | webgl := js.Global().Get("WebGLRenderingContext")
28 | support.WebGLRenderingContext = false
29 | if !webgl.Equal(js.Null()) {
30 | if !webgl.Equal(js.Undefined()) {
31 | support.WebGLRenderingContext = true
32 | }
33 | }
34 | }
35 |
36 | {
37 | webgl2 := js.Global().Get("WebGLRenderingContext2")
38 | support.WebGLRenderingContext2 = false
39 | if !webgl2.Equal(js.Null()) {
40 | if !webgl2.Equal(js.Undefined()) {
41 | support.WebGLRenderingContext2 = true
42 | }
43 | }
44 | }
45 |
46 | return support
47 | }
48 |
49 | func newContext(canvas js.Value, ca *contextAttributes) (js.Value, error) {
50 | support := getWebglSupport()
51 |
52 | attrs := map[string]interface{}{
53 | "alpha": ca.Alpha,
54 | "depth": ca.Depth,
55 | "stencil": ca.Stencil,
56 | "antialias": ca.Antialias,
57 | "premultipliedAlpha": ca.PremultipliedAlpha,
58 | "preserveDrawingBuffer": ca.PreserveDrawingBuffer,
59 | "preferLowPowerToHighPerformance": ca.PreferLowPowerToHighPerformance,
60 | "failIfMajorPerformanceCaveat": ca.FailIfMajorPerformanceCaveat,
61 | }
62 |
63 | gl := canvas.Call("getContext", "webgl2", attrs)
64 | if !gl.Equal(js.Null()) {
65 | return gl, nil
66 | }
67 |
68 | // if !gl.Equal(js.Null()) {
69 | // // ext := gl.Call("getExtension", "WEBGL_lose_context")
70 | // // TODO - this isn't working?
71 | // ext := gl.Call("getExtension", "WEBKIT_WEBGL_depth_texture")
72 | // // ext := gl.Call("getExtension", "WEBGL_depth_texture") // windows?
73 | // log.Println("DepthTexture Extension: ", ext)
74 | // }
75 |
76 | // --- Fallbacks ---
77 | // TODO: Is this worth falling back to?
78 | // fmt.Println("Failed to create webgl2 context, trying experimental-webgl2")
79 | // gl = canvas.Call("getContext", "experimental-webgl2", attrs)
80 | // if !gl.Equal(js.Null()) {
81 | // return gl, nil
82 | // }
83 |
84 | fmt.Println("Failed to create webgl2 context, trying webgl1")
85 | gl = canvas.Call("getContext", "webgl", attrs)
86 | if !gl.Equal(js.Null()) {
87 | return gl, nil
88 | }
89 |
90 | fmt.Println("Failed to create webgl2 and webgl1 context, trying experimental-webgl")
91 | gl = canvas.Call("getContext", "experimental-webgl", attrs)
92 | if !gl.Equal(js.Null()) {
93 | return gl, nil
94 | }
95 |
96 | // TODO: Not sure what WebGLDebugUtils is, or if its worth falling back to
97 | // if !gl.Equal(js.Null()) {
98 | // debug := js.Global().Get("WebGLDebugUtils")
99 | // if debug.Equal(js.Undefined()) {
100 | // return gl, errors.New("No debugging for WebGL.")
101 | // }
102 | // gl = debug.Call("makeDebugContext", gl)
103 | // return gl, nil
104 | // }
105 |
106 | return js.Value{}, fmt.Errorf("webgl context creation error. Browser Support: %s", support.String())
107 | }
108 |
109 | type contextAttributes struct {
110 | Alpha bool
111 | Depth bool
112 | Stencil bool
113 | Antialias bool
114 | PremultipliedAlpha bool
115 | PreserveDrawingBuffer bool
116 | PreferLowPowerToHighPerformance bool
117 | FailIfMajorPerformanceCaveat bool
118 | }
119 |
120 | // https://www.glfw.org/docs/3.3/window_guide.html
121 | func defaultAttributes() *contextAttributes {
122 | return &contextAttributes{
123 | Alpha: false,
124 | Depth: true,
125 | Stencil: false,
126 | Antialias: false,
127 | PremultipliedAlpha: false,
128 | PreserveDrawingBuffer: false,
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/internal/glfw/glfw.go:
--------------------------------------------------------------------------------
1 | // Package glfw experimentally provides a glfw-like API
2 | // with desktop (via glfw) and browser (via HTML5 canvas) backends.
3 | //
4 | // It is used for creating a GL context and receiving events.
5 | //
6 | // Note: This package is currently in development. The API is incomplete and may change.
7 | package glfw
8 |
9 | // ContextWatcher is a general mechanism for being notified when context is made current or detached.
10 | type ContextWatcher interface {
11 | // OnMakeCurrent is called after a context is made current.
12 | // context is is a platform-specific representation of the context, or nil if unavailable.
13 | OnMakeCurrent(context interface{})
14 |
15 | // OnDetach is called after the current context is detached.
16 | OnDetach()
17 | }
18 |
19 | // VidMode describes a single video mode.
20 | type VidMode struct {
21 | Width int // The width, in pixels, of the video mode.
22 | Height int // The height, in pixels, of the video mode.
23 | RedBits int // The bit depth of the red channel of the video mode.
24 | GreenBits int // The bit depth of the green channel of the video mode.
25 | BlueBits int // The bit depth of the blue channel of the video mode.
26 | RefreshRate int // The refresh rate, in Hz, of the video mode.
27 | }
28 |
--------------------------------------------------------------------------------
/internal/glfw/hint_glfw.go:
--------------------------------------------------------------------------------
1 | //go:build !js
2 | // +build !js
3 |
4 | package glfw
5 |
6 | import (
7 | "github.com/go-gl/glfw/v3.3/glfw"
8 | )
9 |
10 | const (
11 | True = glfw.True
12 | False = glfw.False
13 | OpenGLCoreProfile = glfw.OpenGLCoreProfile
14 | )
15 |
16 | type Hint int
17 |
18 | const (
19 | AlphaBits = Hint(glfw.AlphaBits)
20 | DepthBits = Hint(glfw.DepthBits)
21 | StencilBits = Hint(glfw.StencilBits)
22 | Samples = Hint(glfw.Samples)
23 | Resizable = Hint(glfw.Resizable)
24 |
25 | ContextVersionMajor = Hint(glfw.ContextVersionMajor)
26 | ContextVersionMinor = Hint(glfw.ContextVersionMinor)
27 | OpenGLProfile = Hint(glfw.OpenGLProfile)
28 | OpenGLForwardCompatible = Hint(glfw.OpenGLForwardCompatible)
29 |
30 | // These hints used for WebGL contexts, ignored on desktop.
31 | PremultipliedAlpha = noopHint
32 | PreserveDrawingBuffer
33 | PreferLowPowerToHighPerformance
34 | FailIfMajorPerformanceCaveat
35 |
36 | Focused = Hint(glfw.Focused)
37 | Decorated = Hint(glfw.Decorated)
38 | Floating = Hint(glfw.Floating)
39 | AutoIconify = Hint(glfw.AutoIconify)
40 | TransparentFramebuffer = Hint(glfw.TransparentFramebuffer)
41 | Maximized = Hint(glfw.Maximized)
42 | Visible = Hint(glfw.Visible)
43 |
44 | RedBits = Hint(glfw.RedBits)
45 | GreenBits = Hint(glfw.GreenBits)
46 | BlueBits = Hint(glfw.BlueBits)
47 | RefreshRate = Hint(glfw.RefreshRate)
48 | )
49 |
50 | // noopHint is ignored.
51 | const noopHint Hint = -1
52 |
53 | func WindowHint(target Hint, hint int) {
54 | if target == noopHint {
55 | return
56 | }
57 |
58 | glfw.WindowHint(glfw.Hint(target), hint)
59 | }
60 |
--------------------------------------------------------------------------------
/internal/glfw/hint_js.go:
--------------------------------------------------------------------------------
1 | //go:build js
2 | // +build js
3 |
4 | package glfw
5 |
6 | var hints = make(map[Hint]int)
7 |
8 | const (
9 | True = iota
10 | False
11 | OpenGLCoreProfile
12 | )
13 |
14 | type Hint int
15 |
16 | const (
17 | AlphaBits Hint = iota
18 | DepthBits
19 | StencilBits
20 | Samples
21 | Resizable
22 |
23 | ContextVersionMajor // TODO
24 | ContextVersionMinor // TODO
25 | OpenGLProfile // TODO
26 | OpenGLForwardCompatible // TODO
27 |
28 | // goxjs/glfw-specific hints for WebGL.
29 | PremultipliedAlpha
30 | PreserveDrawingBuffer
31 | PreferLowPowerToHighPerformance
32 | FailIfMajorPerformanceCaveat
33 |
34 | Decorated // TODO
35 | Floating // TODO
36 | AutoIconify // TODO
37 | TransparentFramebuffer // TODO
38 | Maximized // TODO
39 | Visible // TODO
40 |
41 | RedBits
42 | GreenBits
43 | BlueBits
44 | RefreshRate
45 | )
46 |
47 | func WindowHint(target Hint, hint int) {
48 | hints[target] = hint
49 | }
50 |
--------------------------------------------------------------------------------
/internal/mainthread/mainthread.go:
--------------------------------------------------------------------------------
1 | //go:build !js
2 | // +build !js
3 |
4 | package mainthread
5 |
6 | // Note: Adapted from - "github.com/faiface/mainthread"
7 | import (
8 | "errors"
9 | "runtime"
10 | )
11 |
12 | var CallQueueCap = 16
13 |
14 | var (
15 | callQueue chan func()
16 | blockingQueue chan func()
17 | blockingQueueDone chan struct{}
18 | )
19 |
20 | func init() {
21 | runtime.LockOSThread()
22 | }
23 |
24 | func checkRun() {
25 | if callQueue == nil {
26 | panic(errors.New("mainthread: did not call Run"))
27 | }
28 | }
29 |
30 | func Run(run func()) {
31 | callQueue = make(chan func(), CallQueueCap)
32 | blockingQueue = make(chan func())
33 | blockingQueueDone = make(chan struct{})
34 |
35 | // panicQueue := make(chan any) // Used to bubble panics from the run function to the caller
36 | done := make(chan error) // TODO - maybe return errors?
37 | go func() {
38 | // defer func() {
39 | // r := recover()
40 | // if r != nil {
41 | // panicQueue <- r
42 | // }
43 | // }()
44 |
45 | run()
46 | done <- nil // TODO - maybe run should return errors
47 | }()
48 |
49 | for {
50 | select {
51 | case f := <-blockingQueue:
52 | f()
53 | blockingQueueDone <- struct{}{}
54 | case f := <-callQueue:
55 | f()
56 | case <-done:
57 | return
58 | // case p := <-panicQueue:
59 | // panic(p)
60 | }
61 | }
62 | }
63 |
64 | func Call(f func()) {
65 | checkRun()
66 | blockingQueue <- f
67 | <-blockingQueueDone
68 | }
69 |
70 | func CallNonBlock(f func()) {
71 | checkRun()
72 | callQueue <- f
73 | }
74 |
75 | func CallErr(f func() error) error {
76 | checkRun()
77 | errChan := make(chan error)
78 | callQueue <- func() {
79 | errChan <- f()
80 | }
81 | return <-errChan
82 | }
83 |
--------------------------------------------------------------------------------
/internal/mainthread/mainthread_js.go:
--------------------------------------------------------------------------------
1 | //go:build js
2 | // +build js
3 |
4 | package mainthread
5 |
6 | // Note: This is an optimization for browsers, which only have one thread.
7 |
8 | func Run(run func()) {
9 | run()
10 | }
11 |
12 | func Call(f func()) {
13 | f()
14 | }
15 |
16 | func CallNonBlock(f func()) {
17 | go f()
18 | }
19 |
20 | func CallErr(f func() error) error {
21 | return f()
22 | }
23 |
--------------------------------------------------------------------------------
/model.go:
--------------------------------------------------------------------------------
1 | package glitch
2 |
3 | type Model struct {
4 | mesh *Mesh
5 | material Material
6 | }
7 |
8 | func NewModel(mesh *Mesh, material Material) *Model {
9 | return &Model{
10 | mesh: mesh,
11 | material: material,
12 | }
13 | }
14 |
15 | func (m *Model) Draw(target BatchTarget, matrix Mat4) {
16 | m.DrawColorMask(target, matrix, White)
17 | }
18 | func (m *Model) DrawColorMask(target BatchTarget, matrix Mat4, mask RGBA) {
19 | target.Add(m.mesh.g(), glm4(matrix), mask, m.material)
20 | }
21 |
--------------------------------------------------------------------------------
/shaders/diffuse.fs:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | #ifdef GL_ES
4 | precision highp float;
5 | #endif
6 |
7 | struct Material {
8 | vec3 ambient;
9 | vec3 diffuse;
10 | vec3 specular;
11 | float shininess;
12 | };
13 |
14 | struct DirLight {
15 | vec3 direction;
16 | vec3 ambient;
17 | vec3 diffuse;
18 | vec3 specular;
19 | };
20 |
21 | struct PointLight {
22 | vec3 position;
23 | float constant;
24 | float linear;
25 | float quadratic;
26 | vec3 ambient;
27 | vec3 diffuse;
28 | vec3 specular;
29 | };
30 |
31 | struct SpotLight {
32 | vec3 position;
33 | vec3 direction;
34 | float cutOff;
35 | float outerCutOff;
36 | float constant;
37 | float linear;
38 | float quadratic;
39 | vec3 ambient;
40 | vec3 diffuse;
41 | vec3 specular;
42 | };
43 |
44 | out vec4 FragColor;
45 | in vec3 FragPos;
46 | in vec3 Normal;
47 | in vec2 TexCoord;
48 | in vec4 FragPosLightSpace;
49 |
50 | uniform vec3 viewPos;
51 | uniform Material material;
52 | uniform DirLight dirLight;
53 | #define NR_POINT_LIGHTS 1
54 | uniform PointLight pointLights[NR_POINT_LIGHTS];
55 | uniform sampler2D tex;
56 | uniform sampler2D shadowMap;
57 |
58 | vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
59 | vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
60 | vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
61 |
62 | void main()
63 | {
64 | // FragColor = vec4(0.0, 1.0, 0.0, 1.0);
65 | // FragColor = vec4(ourColor, 1.0);
66 | // FragColor = texture(tex, TexCoord) * vec4(ourColor, 1.0);
67 | // FragColor = texture(tex, TexCoord);
68 | // // ambient
69 | // vec3 ambient = light.ambient * material.ambient;
70 | // // diffuse
71 | // vec3 norm = normalize(Normal);
72 | // vec3 lightDir = normalize(light.position - FragPos);
73 | // float diff = max(dot(norm, lightDir), 0.0);
74 | // vec3 diffuse = light.diffuse * (diff * material.diffuse);
75 | // // specular
76 | // vec3 viewDir = normalize(viewPos - FragPos);
77 | // vec3 reflectDir = reflect(-lightDir, norm);
78 | // float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
79 | // vec3 specular = light.specular * (spec * material.specular);
80 | // // vec3 result = texture(tex, TexCoord).xyz;
81 | // vec3 result = ambient + diffuse + specular;
82 | // Calculate a few properties
83 | vec3 norm = normalize(Normal);
84 | vec3 viewDir = normalize(viewPos - FragPos);
85 | // Calculate Directional Lighting
86 | vec3 result = CalcDirLight(dirLight, norm, viewDir);
87 | // Calculate Point Lighting
88 | //for(int i = 0; i < NR_POINT_LIGHTS; i++)
89 | // result += normalize(CalcPointLight(pointLights[i], norm, FragPos, viewDir));
90 | // Calculate Spot Lighting
91 | // result += CalcSpotLight(spotLight, norm, FragPos, viewDir);
92 | vec3 color = result * vec3(0.2, 0.2, 0.2);
93 | FragColor = vec4(color, 1.0);
94 | // Linearize Depth Buffer:
95 | // float near = 0.1;
96 | // float far = 100.0;
97 | // float depth = gl_FragCoord.z;
98 | // float z = depth * 2.0 - 1.0; // back to NDC
99 | // depth = (2.0 * near * far) / (far + near - z * (far - near));
100 | // depth = depth / far; // divide by far for demonstration
101 | // FragColor += vec4(vec3(depth), 1.0);
102 | // Display Depth Buffer: FragColor += vec4(vec3(gl_FragCoord.z), 1.0);
103 | //FragColor += vec4(0.3, 0.3, 0.3, 0.0);
104 | //FragColor += texture(tex, TexCoord);
105 | // FragColor *= texture(tex, TexCoord);
106 | // FragColor = vec4(FragColor.rgb, 1.0);
107 | }
108 | float ShadowCalculation(vec4 fragPosLightSpace)
109 | {
110 | // perform perspective divide
111 | vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
112 | // transform to [0,1] range
113 | projCoords = projCoords * 0.5 + 0.5;
114 | // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords)
115 | float closestDepth = texture(shadowMap, projCoords.xy).r;
116 | // get depth of current fragment from light's perspective
117 | float currentDepth = projCoords.z;
118 | // check whether current frag pos is in shadow
119 | float shadow = currentDepth > closestDepth ? 1.0 : 0.0;
120 | return shadow;
121 | }
122 | vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
123 | {
124 | vec3 lightDir = normalize(-light.direction);
125 | // vec3 lightDir = normalize(light.direction - FragPos);
126 | // diffuse shading
127 | float diff = max(dot(normal, lightDir), 0.0);
128 | // specular shading
129 | vec3 reflectDir = reflect(-lightDir, normal);
130 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
131 | // combine results
132 | // For Textures: vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
133 | // For Textures: vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
134 | // For SpecMaps: vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
135 | vec3 ambient = light.ambient * material.ambient;
136 | vec3 diffuse = light.diffuse * (diff * material.diffuse);
137 | vec3 specular = light.specular * (spec * material.specular);
138 | //old way: return (ambient + diffuse + specular);
139 | // with shadows:
140 | float shadow = ShadowCalculation(FragPosLightSpace);
141 | return (ambient + (1.0 - shadow) * (diffuse + specular));
142 | }
143 | // calculates the color when using a point light.
144 | vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
145 | {
146 | vec3 lightDir = normalize(light.position - fragPos);
147 | // diffuse shading
148 | float diff = max(dot(normal, lightDir), 0.0);
149 | // specular shading
150 | vec3 reflectDir = reflect(-lightDir, normal);
151 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
152 | // attenuation
153 | float distance = length(light.position - fragPos);
154 | float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
155 | // combine results
156 | // For Textures: vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
157 | // For Textures: vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
158 | // For SpecMaps: vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
159 | vec3 ambient = light.ambient * material.ambient;
160 | vec3 diffuse = light.diffuse * (diff * material.diffuse);
161 | vec3 specular = light.specular * (spec * material.specular);
162 | ambient *= attenuation;
163 | diffuse *= attenuation;
164 | specular *= attenuation;
165 | // return (ambient + diffuse + specular);
166 | vec3 res = 0.01 * (ambient + diffuse + specular);
167 | return res;
168 | }
169 | // calculates the color when using a spot light.
170 | vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
171 | {
172 | vec3 lightDir = normalize(light.position - fragPos);
173 | // diffuse shading
174 | float diff = max(dot(normal, lightDir), 0.0);
175 | // specular shading
176 | vec3 reflectDir = reflect(-lightDir, normal);
177 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
178 | // attenuation
179 | float distance = length(light.position - fragPos);
180 | float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
181 | // spotlight intensity
182 | float theta = dot(lightDir, normalize(-light.direction));
183 | float epsilon = light.cutOff - light.outerCutOff;
184 | float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
185 | // combine results
186 | // For Textures: vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
187 | // For Textures: vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
188 | // For SpecMaps: vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
189 | vec3 ambient = light.ambient * material.ambient;
190 | vec3 diffuse = light.diffuse * (diff * material.diffuse);
191 | vec3 specular = light.specular * (spec * material.specular);
192 | ambient *= attenuation * intensity;
193 | diffuse *= attenuation * intensity;
194 | specular *= attenuation * intensity;
195 | return (ambient + diffuse + specular);
196 | }
197 |
198 | // #version 300 es
199 | // // Required for webgl
200 | // #ifdef GL_ES
201 | // precision highp float;
202 | // #endif
203 |
204 | // out vec4 FragColor;
205 |
206 | // in vec4 ourColor;
207 | // in vec2 TexCoord;
208 |
209 | // //texture samplers
210 | // uniform sampler2D texture1;
211 |
212 | // void main()
213 | // {
214 | // vec4 tex = texture(texture1, TexCoord);
215 |
216 | // // linearly interpolate between both textures (80% container, 20% awesomeface)
217 | // //FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
218 | // FragColor = ourColor * tex;
219 | // // FragColor = vec4(ourColor, 1.0) * texture(texture1, TexCoord);
220 | // // FragColor = vec4(ourColor, 1.0);
221 | // }
222 |
--------------------------------------------------------------------------------
/shaders/flat.fs:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | // Required for webgl
4 | #ifdef GL_ES
5 | precision highp float;
6 | #endif
7 |
8 | struct Material {
9 | vec3 ambient;
10 | vec3 diffuse;
11 | vec3 specular;
12 | float shininess;
13 | };
14 |
15 | struct DirLight {
16 | vec3 direction;
17 | vec3 ambient;
18 | vec3 diffuse;
19 | vec3 specular;
20 | };
21 |
22 | out vec4 FragColor;
23 |
24 | in vec3 FragPos;
25 | in vec3 Normal;
26 | in vec2 TexCoord;
27 | /* in vec4 FragPosLightSpace; */
28 |
29 | uniform vec3 viewPos;
30 | uniform Material material;
31 | uniform DirLight dirLight;
32 |
33 | uniform sampler2D tex;
34 |
35 | void main()
36 | {
37 | // ambient
38 | vec3 ambient = dirLight.ambient * material.ambient;
39 |
40 | // diffuse
41 | vec3 norm = normalize(Normal);
42 | /* vec3 lightDir = normalize(light.position - FragPos); */ /* Calculate from position */
43 | vec3 lightDir = normalize(dirLight.direction);
44 | float diff = max(dot(norm, lightDir), 0.0);
45 | vec3 diffuse = dirLight.diffuse * (diff * material.diffuse);
46 |
47 | // specular
48 | vec3 viewDir = normalize(viewPos - FragPos);
49 | vec3 reflectDir = reflect(-lightDir, norm);
50 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
51 | vec3 specular = dirLight.specular * (spec * material.specular);
52 |
53 | vec3 result = ambient + diffuse + specular;
54 | FragColor = vec4(result, 1.0);
55 |
56 | /* TODO re-enable texture */
57 | /* FragColor = texture(tex, TexCoord); */
58 | }
59 |
--------------------------------------------------------------------------------
/shaders/mesh.vs:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | layout (location = 0) in vec3 positionIn;
4 | /* layout (location = 1) in vec4 colorIn; */
5 | layout (location = 1) in vec3 normalIn;
6 | layout (location = 2) in vec2 texCoordIn;
7 |
8 | out vec3 FragPos;
9 | out vec3 Normal;
10 | out vec2 TexCoord;
11 | //out vec4 FragPosLightSpace;
12 |
13 | uniform mat4 model;
14 | uniform mat4 view;
15 | uniform mat4 projection;
16 | // uniform vec3 viewPos;
17 |
18 | //uniform mat4 shadowMatrix;
19 |
20 | void main()
21 | {
22 | /* TexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y); */
23 | /* FragPosLightSpace = shadowMatrix * vec4(FragPos, 1.0); */
24 |
25 | Normal = mat3(transpose(inverse(model))) * normalIn; // I didn't really understand this
26 | FragPos = vec3(model * vec4(positionIn, 1.0f));
27 | TexCoord = texCoordIn;
28 |
29 | gl_Position = projection * view * model * vec4(positionIn, 1.0f);
30 | }
31 |
--------------------------------------------------------------------------------
/shaders/minimap.fs:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | // Required for webgl
4 | #ifdef GL_ES
5 | precision highp float;
6 | #endif
7 |
8 | out vec4 FragColor;
9 |
10 | in vec4 ourColor;
11 | in vec2 TexCoord;
12 |
13 | /* // View matrix uniform */
14 | /* uniform mat4 view; */
15 |
16 | //texture samplers
17 | uniform sampler2D texture1;
18 |
19 | float normpdf(float x, float sigma) {
20 | return 0.39894*exp(-0.5*x*x/(sigma*sigma))/sigma;
21 | }
22 |
23 | void main()
24 | {
25 | /* /\* float s = 16.0; *\/ */
26 | /* /\* float sHalf = 8.0; *\/ */
27 | /* /\* vec4 color = texture(texture1, (floor(TexCoord * s)+sHalf)/s); *\/ */
28 | /* /\* vec4 color = texture(texture1, (floor(TexCoord * s)+.5)/s); *\/ */
29 | /* vec4 color = texture(texture1, TexCoord); */
30 |
31 | /* /\* // Reduce color range *\/ */
32 | /* /\* float range = 3.0; *\/ */
33 | /* /\* color = floor(color * range) / range; *\/ */
34 |
35 | /* if (color.a == 0.0) { */
36 | /* discard; */
37 | /* } */
38 |
39 | /* FragColor = ourColor * color; */
40 |
41 | vec2 resolution = vec2(2048.0, 2048.0);
42 | vec3 c = texture(texture1, TexCoord.xy / resolution.xy).rgb;
43 |
44 | //declare stuff
45 | const int mSize = 11;
46 | const int kSize = (mSize-1)/2;
47 | float kernel[mSize];
48 | vec3 final_colour = vec3(0.0);
49 |
50 | //create the 1-D kernel
51 | float sigma = 7.0;
52 | float Z = 0.0;
53 | for (int j = 0; j <= kSize; ++j) {
54 | kernel[kSize+j] = kernel[kSize-j] = normpdf(float(j), sigma);
55 | }
56 |
57 | //get the normalization factor (as the gaussian has been clamped)
58 | for (int j = 0; j < mSize; ++j) {
59 | Z += kernel[j];
60 | }
61 |
62 | //read out the texels
63 | for (int i=-kSize; i <= kSize; ++i) {
64 | for (int j=-kSize; j <= kSize; ++j) {
65 | final_colour += kernel[kSize+j]*kernel[kSize+i]*texture(texture1, (TexCoord.xy+vec2(float(i),float(j))) / resolution.xy).rgb;
66 | }
67 | }
68 |
69 | FragColor = ourColor * vec4(final_colour/(Z*Z), 1.0);
70 | }
71 |
--------------------------------------------------------------------------------
/shaders/msdf.fs:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | // Adapted from: https://www.redblobgames.com/x/2404-distance-field-effects/distance-field-effects.js
4 |
5 | // Required for webgl
6 | #ifdef GL_ES
7 | precision highp float;
8 | #endif
9 |
10 | out vec4 FragColor;
11 |
12 | in vec4 ourColor;
13 | in vec2 TexCoord;
14 |
15 | uniform sampler2D texture1;
16 |
17 | //#extension GL_OES_standard_derivatives : enable
18 | //precision mediump float;
19 |
20 | /* varying vec2 v_texcoord; */
21 | /* uniform sampler2D u_mtsdf_font; */
22 |
23 | /* uniform vec2 u_unit_range; */
24 | /* uniform float u_rounded_fonts; */
25 | /* uniform float u_rounded_outlines; */
26 | uniform float u_threshold;
27 | /* uniform float u_out_bias; */
28 | /* uniform float u_outline_width_absolute; */
29 | uniform float u_outline_width_relative;
30 | uniform float u_outline_blur;
31 | /* uniform float u_gradient; */
32 | /* uniform float u_gamma; */
33 | uniform vec4 u_outline_color;
34 |
35 | /* sample code from https://github.com/Chlumsky/msdfgen */
36 | float median(float r, float g, float b) {
37 | return max(min(r, g), min(max(r, g), b));
38 | }
39 |
40 | float screenPxRange() {
41 | float distanceRange = 10.0;
42 | vec2 texSize = vec2(textureSize(texture1, 0));
43 | vec2 u_unit_range = vec2(distanceRange / texSize.x, distanceRange / texSize.y);
44 | vec2 screenTexSize = vec2(1.0) / fwidth(TexCoord);
45 | return max(0.5 * dot(u_unit_range, screenTexSize), 1.0);
46 | }
47 |
48 | void main() {
49 | float u_rounded_fonts = 0.0;
50 | float u_rounded_outlines = 0.0;
51 | /* float u_threshold = 0.5; */
52 | float u_out_bias = 0.25;
53 | float u_outline_width_absolute = 0.3;
54 | /* float u_outline_width_relative = 0.05; */
55 | /* float u_outline_blur = 0.0; */
56 | float u_gradient = 0.0;
57 | float u_gamma = 1.0;
58 |
59 | /* vec4 inner_color = vec4(1, 1, 1, 1); */
60 | /* vec4 outer_color = vec4(0, 0, 0, 1); */
61 | vec4 inner_color = ourColor;
62 | vec4 outer_color = u_outline_color * ourColor;
63 |
64 | // distances are stored with 1.0 meaning "inside" and 0.0 meaning "outside"
65 | vec4 distances = texture(texture1, TexCoord);
66 | float d_msdf = median(distances.r, distances.g, distances.b);
67 | float d_sdf = distances.a; // mtsdf format only
68 | d_msdf = min(d_msdf, d_sdf + 0.1); // HACK: to fix glitch in msdf near edges
69 |
70 | // blend between sharp and rounded corners
71 | float d_inner = mix(d_msdf, d_sdf, u_rounded_fonts);
72 | float d_outer = mix(d_msdf, d_sdf, u_rounded_outlines);
73 |
74 | // typically 0.5 is the threshold, >0.5 inside <0.5 outside
75 | float inverted_threshold = 1.0 - u_threshold; // because I want the ui to be +larger -smaller
76 | float width = screenPxRange();
77 | float inner = width * (d_inner - inverted_threshold) + 0.5 + u_out_bias;
78 | float outer = width * (d_outer - inverted_threshold + u_outline_width_relative) + 0.5 + u_out_bias + u_outline_width_absolute;
79 |
80 | float inner_opacity = clamp(inner, 0.0, 1.0);
81 | float outer_opacity = clamp(outer, 0.0, 1.0);
82 |
83 | if (u_outline_blur > 0.0) {
84 | // NOTE: the smoothstep fails when the two edges are the same, and I wish it
85 | // would act like a step function instead of failing.
86 | // NOTE: I'm using d_sdf here because I want the shadows to be rounded
87 | // even when outlines are sharp. But I don't yet have implemented a way
88 | // to see the sharp outline with a rounded shadow.
89 | float blur_start = u_outline_width_relative + u_outline_width_absolute / width;
90 | outer_color.a = smoothstep(blur_start,
91 | blur_start * (1.0 - u_outline_blur),
92 | inverted_threshold - d_sdf - u_out_bias / width);
93 | }
94 |
95 | // apply some lighting (hard coded angle)
96 | if (u_gradient > 0.0) {
97 | // NOTE: this is not a no-op so it changes the rendering even when
98 | // u_gradient is 0.0. So I use an if() instead. But ideally I'd
99 | // make this do nothing when u_gradient is 0.0.
100 | vec2 normal = normalize(vec3(dFdx(d_inner), dFdy(d_inner), 0.01)).xy;
101 | float light = 0.5 * (1.0 + dot(normal, normalize(vec2(-0.3, -0.5))));
102 | inner_color = mix(inner_color, vec4(light, light, light, 1),
103 | smoothstep(u_gradient + inverted_threshold, inverted_threshold, d_inner));
104 | }
105 |
106 | // unlike in the 2403 experiments, I do know the color is light
107 | // and the shadow is dark so I can implement gamma correction
108 | inner_opacity = pow(inner_opacity, 1.0 / u_gamma);
109 |
110 | vec4 color = (inner_color * inner_opacity) + (outer_color * (outer_opacity - inner_opacity));
111 |
112 | if (color.a == 0.0) {
113 | discard;
114 | }
115 |
116 | FragColor = color;
117 | }
118 |
119 | /* #version 300 es */
120 |
121 | /* // Required for webgl */
122 | /* #ifdef GL_ES */
123 | /* precision highp float; */
124 | /* #endif */
125 |
126 | /* out vec4 FragColor; */
127 |
128 | /* in vec4 ourColor; */
129 | /* in vec2 TexCoord; */
130 |
131 | /* uniform sampler2D texture1; */
132 | /* /\* uniform vec4 bgColor; *\/ */
133 | /* /\* uniform vec4 fgColor; *\/ */
134 |
135 | /* // https://github.com/Chlumsky/msdfgen?tab=readme-ov-file#using-a-multi-channel-distance-field */
136 | /* float median(float r, float g, float b) { */
137 | /* return max(min(r, g), min(max(r, g), b)); */
138 | /* } */
139 |
140 | /* void main() { */
141 | /* float screenPxRange = 2.5; */
142 | /* float u_in_bias = 0.2; */
143 | /* float u_out_bias = 0.2; */
144 | /* float u_outline = 0.1; */
145 |
146 | /* vec4 inner_color = vec4(1, 1, 1, 1); */
147 | /* vec4 outer_color = vec4(1, 0, 0, 1); */
148 |
149 |
150 | /* vec4 msd = texture(texture1, TexCoord); */
151 | /* float sd = median(msd.r, msd.g, msd.b); */
152 | /* float width = screenPxRange; */
153 | /* float inner = width * (sd - 0.5 + u_in_bias) + 0.5 + u_out_bias; */
154 | /* float outer = width * (sd - 0.5 + u_in_bias + u_outline) + 0.5 + u_out_bias; */
155 |
156 | /* float inner_opacity = clamp(inner, 0.0, 1.0); */
157 | /* float outer_opacity = clamp(outer, 0.0, 1.0); */
158 |
159 | /* FragColor = (inner_color * inner_opacity) + (outer_color * outer_opacity); */
160 |
161 |
162 |
163 | /* /\* float screenPxRange = 2.5; *\/ */
164 | /* /\* float screenPxRange2 = 2.5 *\/ */
165 |
166 | /* /\* vec4 bgColor = vec4(1.0, 0.0, 0.0, 1.0); *\/ */
167 | /* /\* vec4 fgColor = vec4(0.0, 1.0, 0.0, 1.0); *\/ */
168 |
169 | /* /\* float borderWidth = 0.5; *\/ */
170 | /* /\* /\\* float borderEdge = 0.1; *\\/ *\/ */
171 | /* /\* /\\* vec4 borderColor = vec3(1.0, 0.0, 0.0); *\\/ *\/ */
172 |
173 |
174 | /* /\* vec3 msd = texture(texture1, TexCoord).rgb; *\/ */
175 | /* /\* float sd = median(msd.r, msd.g, msd.b); *\/ */
176 | /* /\* float screenPxDistance = screenPxRange * (sd - 0.5); *\/ */
177 | /* /\* float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); *\/ */
178 |
179 | /* /\* float screenPxDistance2 = screenPxRange2 * (sd - borderWidth); *\/ */
180 | /* /\* float opacity2 = clamp(screenPxDistance2 + borderWidth, 0.0, 1.0); *\/ */
181 |
182 | /* /\* float finalAlpha = opacity + (1.0 - opacity) * opacity2; *\/ */
183 | /* /\* vec4 finalColor = mix(fgColor, bgColor, opacity / finalAlpha); *\/ */
184 |
185 | /* /\* FragColor = finalColor; *\/ */
186 | /* } */
187 |
188 | /* /\* void main() { *\/ */
189 | /* /\* float screenPxRange = 4.5; *\/ */
190 | /* /\* vec4 bgColor = vec4(0.0, 0.0, 0.0, 0.0); *\/ */
191 | /* /\* vec4 fgColor = vec4(0.0, 1.0, 0.0, 1.0); *\/ */
192 |
193 |
194 | /* /\* vec3 msd = texture(texture1, TexCoord).rgb; *\/ */
195 | /* /\* float sd = median(msd.r, msd.g, msd.b); *\/ */
196 | /* /\* float screenPxDistance = screenPxRange*(sd - 0.5); *\/ */
197 | /* /\* float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); *\/ */
198 | /* /\* FragColor = mix(bgColor, fgColor, opacity); *\/ */
199 | /* /\* } *\/ */
200 |
201 | /* //RBG */
202 | /* /\* void main() { *\/ */
203 | /* /\* vec4 distances = texture2D(u_mtsdf_font, v_texcoord); *\/ */
204 | /* /\* float d = median(distances.r, distances.g, distances.b); *\/ */
205 | /* /\* float width = screenPxRange(); *\/ */
206 | /* /\* float inner = width * (d - 0.5 + u_in_bias ) + 0.5 + u_out_bias; *\/ */
207 | /* /\* float outer = width * (d - 0.5 + u_in_bias + u_outline) + 0.5 + u_out_bias; *\/ */
208 |
209 | /* /\* float inner_opacity = clamp(inner, 0.0, 1.0); *\/ */
210 | /* /\* vec4 inner_color = vec4(1, 1, 1, 1); *\/ */
211 | /* /\* float outer_opacity = clamp(outer, 0.0, 1.0); *\/ */
212 | /* /\* vec4 outer_color = vec4(0, 0, 0, 1); *\/ */
213 |
214 | /* /\* vec4 color = (inner_color * inner_opacity) + (outer_color * outer_opacity); *\/ */
215 | /* /\* gl_FragColor = color; *\/ */
216 | /* /\* } *\/ */
217 |
--------------------------------------------------------------------------------
/shaders/pixel.fs:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | // Required for webgl
4 | #ifdef GL_ES
5 | precision highp float;
6 | #endif
7 |
8 | out vec4 FragColor;
9 |
10 | in vec4 ourColor;
11 | in vec2 TexCoord;
12 |
13 | //texture samplers
14 | uniform sampler2D texture1;
15 |
16 | void main()
17 | {
18 | vec4 tex = texture(texture1, TexCoord);
19 | if (tex.a == 0.0) {
20 | discard;
21 | }
22 | // linearly interpolate between both textures (80% container, 20% awesomeface)
23 | //FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
24 | FragColor = ourColor * tex;
25 | // FragColor = vec4(ourColor, 1.0) * texture(texture1, TexCoord);
26 | // FragColor = vec4(ourColor, 1.0);
27 | }
28 |
--------------------------------------------------------------------------------
/shaders/pixel.vs:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | layout (location = 0) in vec3 positionIn;
4 | layout (location = 1) in vec4 colorIn;
5 | layout (location = 2) in vec2 texCoordIn;
6 |
7 | out vec4 ourColor;
8 | out vec2 TexCoord;
9 |
10 | uniform mat4 model;
11 | uniform mat4 projection;
12 | uniform mat4 view;
13 | //uniform mat4 transform;
14 |
15 | void main()
16 | {
17 | gl_Position = projection * view * model * vec4(positionIn, 1.0);
18 |
19 | /* // Snap pixels */
20 | /* vec2 pos = vec2(round(positionIn.x), round(positionIn.y)); */
21 |
22 | /* // Apply camera */
23 | /* gl_Position = projection * view * vec4(positionIn, 0.0, 1.0); */
24 |
25 | // gl_Position = projection * transform * vec4(aPos, 1.0);
26 | // gl_Position = vec4(aPos, 1.0);
27 | ourColor = colorIn;
28 |
29 | TexCoord = vec2(texCoordIn.x, texCoordIn.y);
30 | }
31 |
32 |
33 | /* // Old: Used Vec2 as position, ie pre-depth buffer additions */
34 | /* #version 300 es */
35 |
36 | /* layout (location = 0) in vec2 positionIn; */
37 | /* layout (location = 1) in vec4 colorIn; */
38 | /* layout (location = 2) in vec2 texCoordIn; */
39 |
40 | /* out vec4 ourColor; */
41 | /* out vec2 TexCoord; */
42 |
43 | /* uniform mat4 projection; */
44 | /* uniform mat4 view; */
45 | /* //uniform mat4 transform; */
46 |
47 | /* void main() */
48 | /* { */
49 | /* gl_Position = projection * view * vec4(positionIn, 0.0, 1.0); */
50 |
51 | /* /\* // Snap pixels *\/ */
52 | /* /\* vec2 pos = vec2(round(positionIn.x), round(positionIn.y)); *\/ */
53 |
54 | /* /\* // Apply camera *\/ */
55 | /* /\* gl_Position = projection * view * vec4(positionIn, 0.0, 1.0); *\/ */
56 |
57 | /* // gl_Position = projection * transform * vec4(aPos, 1.0); */
58 | /* // gl_Position = vec4(aPos, 1.0); */
59 | /* ourColor = colorIn; */
60 |
61 | /* TexCoord = vec2(texCoordIn.x, texCoordIn.y); */
62 | /* } */
63 |
64 |
--------------------------------------------------------------------------------
/shaders/sdf.fs:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | // Required for webgl
4 | #ifdef GL_ES
5 | precision highp float;
6 | #endif
7 |
8 | out vec4 FragColor;
9 |
10 | in vec4 ourColor;
11 | in vec2 TexCoord;
12 |
13 | uniform sampler2D texture1;
14 |
15 | /* uniform vec4 bgColor; */
16 | /* uniform vec4 fgColor; */
17 |
18 | void main() {
19 | float width = 0.4;
20 | float edge = 0.1;
21 |
22 | float borderWidth = 0.5;
23 | float borderEdge = 0.1;
24 |
25 | vec3 bgColor = vec3(0, 0, 0);
26 | vec3 fgColor = vec3(1, 1, 1);
27 |
28 | float sd = 1.0 - texture(texture1, TexCoord).r;
29 | float alpha = 1.0 - smoothstep(width, width + edge, sd);
30 | float borderAlpha = 1.0 - smoothstep(borderWidth, borderWidth + borderEdge, sd);
31 |
32 | float overallAlpha = alpha + (1.0 - alpha) * borderAlpha;
33 | vec3 mixColor = mix(bgColor, fgColor, alpha / overallAlpha);
34 |
35 | FragColor = vec4(mixColor, overallAlpha);
36 | }
37 |
--------------------------------------------------------------------------------
/shaders/shaders.go:
--------------------------------------------------------------------------------
1 | package shaders
2 |
3 | import (
4 | _ "embed"
5 | "fmt"
6 | )
7 |
8 | type ShaderConfig struct {
9 | VertexShader, FragmentShader string
10 | VertexFormat VertexFormat
11 | UniformFormat UniformFormat
12 | }
13 |
14 | // TODO - right now we only support floats (for simplicity)
15 | type VertexFormat []VertexAttr
16 | type UniformFormat []Attr
17 |
18 | type VertexAttr struct {
19 | Attr // The underlying Attribute
20 | Swizzle SwizzleType // This defines how the shader wants to map a generic object (like a mesh, to the shader buffers)
21 | }
22 |
23 | type Attr struct {
24 | Name string
25 | Type AttrType
26 | }
27 |
28 | // Returns the size of the attribute type
29 | func (a Attr) Size() int {
30 | switch a.Type {
31 | case AttrInt:
32 | return 1
33 | case AttrFloat:
34 | return 1
35 | case AttrVec2:
36 | return 2
37 | case AttrVec3:
38 | return 3
39 | case AttrVec4:
40 | return 4
41 | case AttrMat2:
42 | return 2 * 2
43 | case AttrMat23:
44 | return 2 * 3
45 | case AttrMat24:
46 | return 2 * 4
47 | case AttrMat3:
48 | return 3 * 3
49 | case AttrMat32:
50 | return 3 * 2
51 | case AttrMat34:
52 | return 3 * 4
53 | case AttrMat4:
54 | return 4 * 4
55 | case AttrMat42:
56 | return 4 * 2
57 | case AttrMat43:
58 | return 4 * 3
59 | default:
60 | panic(fmt.Sprintf("Invalid Attribute: %v", a))
61 | }
62 | }
63 |
64 | // This type is used to define the underlying data type of a vertex attribute or uniform attribute
65 | type AttrType uint8
66 |
67 | const (
68 | // TODO - others
69 | AttrInt AttrType = iota
70 | AttrFloat
71 | AttrVec2
72 | AttrVec3
73 | AttrVec4
74 | AttrMat2
75 | AttrMat23
76 | AttrMat24
77 | AttrMat3
78 | AttrMat32
79 | AttrMat34
80 | AttrMat4
81 | AttrMat42
82 | AttrMat43
83 | )
84 |
85 | // This type is used to define how generic meshes map into specific shader buffers
86 | type SwizzleType uint8
87 |
88 | const (
89 | PositionXY SwizzleType = iota
90 | PositionXYZ
91 | NormalXY
92 | NormalXYZ
93 | ColorR
94 | ColorRG
95 | ColorRGB
96 | ColorRGBA
97 | TexCoordXY
98 | // TexCoordXYZ // Is this a thing?
99 | )
100 |
101 | func VertexAttribute(name string, Type AttrType, swizzle SwizzleType) VertexAttr {
102 | return VertexAttr{
103 | Attr: Attr{
104 | Name: name,
105 | Type: Type,
106 | },
107 | Swizzle: swizzle,
108 | }
109 | }
110 |
111 | // Commented out: This was for webgl1 mode
112 | // //go:embed sprite_100.vs
113 | // var SpriteVertexShaderWebGL1 string;
114 |
115 | // //go:embed sprite_100.fs
116 | // var SpriteFragmentShaderWebGL1 string;
117 |
118 | // var SpriteShader = ShaderConfig{
119 | // VertexShader: SpriteVertexShaderWebGL1,
120 | // FragmentShader: SpriteFragmentShaderWebGL1,
121 | // VertexFormat: VertexFormat{
122 | // VertexAttribute("positionIn", AttrVec2, PositionXY),
123 | // VertexAttribute("colorIn", AttrVec4, ColorRGBA),
124 | // VertexAttribute("texCoordIn", AttrVec2, TexCoordXY),
125 | // },
126 | // UniformFormat: UniformFormat{
127 | // Attr{"projection", AttrMat4},
128 | // Attr{"view", AttrMat4},
129 | // },
130 | // }
131 |
132 | //go:embed sprite.vs
133 | var SpriteVertexShader string
134 |
135 | //go:embed sprite.fs
136 | var SpriteFragmentShader string
137 |
138 | var SpriteShader = ShaderConfig{
139 | VertexShader: SpriteVertexShader,
140 | FragmentShader: SpriteFragmentShader,
141 | VertexFormat: VertexFormat{
142 | VertexAttribute("positionIn", AttrVec3, PositionXYZ),
143 | VertexAttribute("colorIn", AttrVec4, ColorRGBA),
144 | VertexAttribute("texCoordIn", AttrVec2, TexCoordXY),
145 | },
146 | UniformFormat: UniformFormat{
147 | Attr{"model", AttrMat4},
148 | Attr{"projection", AttrMat4},
149 | Attr{"view", AttrMat4},
150 | // Attr{"silhouetteMix", AttrFloat},
151 | },
152 | }
153 |
154 | //go:embed msdf.fs
155 | var MSDFFragmentShader string
156 |
157 | var MSDFShader = ShaderConfig{
158 | VertexShader: SpriteVertexShader,
159 | FragmentShader: MSDFFragmentShader,
160 | VertexFormat: VertexFormat{
161 | VertexAttribute("positionIn", AttrVec3, PositionXYZ),
162 | VertexAttribute("colorIn", AttrVec4, ColorRGBA),
163 | VertexAttribute("texCoordIn", AttrVec2, TexCoordXY),
164 | },
165 | UniformFormat: UniformFormat{
166 | Attr{"model", AttrMat4},
167 | Attr{"projection", AttrMat4},
168 | Attr{"view", AttrMat4},
169 | Attr{"u_threshold", AttrFloat},
170 | Attr{"u_outline_width_relative", AttrFloat},
171 | Attr{"u_outline_blur", AttrFloat},
172 | Attr{"u_outline_color", AttrVec4},
173 | },
174 | }
175 |
176 | //go:embed sdf.fs
177 | var SDFFragmentShader string
178 |
179 | var SDFShader = ShaderConfig{
180 | VertexShader: SpriteVertexShader,
181 | FragmentShader: SDFFragmentShader,
182 | VertexFormat: VertexFormat{
183 | VertexAttribute("positionIn", AttrVec3, PositionXYZ),
184 | VertexAttribute("colorIn", AttrVec4, ColorRGBA),
185 | VertexAttribute("texCoordIn", AttrVec2, TexCoordXY),
186 | },
187 | UniformFormat: UniformFormat{
188 | Attr{"model", AttrMat4},
189 | Attr{"projection", AttrMat4},
190 | Attr{"view", AttrMat4},
191 | },
192 | }
193 |
194 | //go:embed minimap.fs
195 | var MinimapFragmentShader string
196 |
197 | var MinimapShader = ShaderConfig{
198 | VertexShader: SpriteVertexShader,
199 | FragmentShader: MinimapFragmentShader,
200 | VertexFormat: VertexFormat{
201 | VertexAttribute("positionIn", AttrVec3, PositionXYZ),
202 | VertexAttribute("colorIn", AttrVec4, ColorRGBA),
203 | VertexAttribute("texCoordIn", AttrVec2, TexCoordXY),
204 | },
205 | UniformFormat: UniformFormat{
206 | Attr{"model", AttrMat4},
207 | Attr{"projection", AttrMat4},
208 | Attr{"view", AttrMat4},
209 | },
210 | }
211 |
212 | //go:embed subPixel.fs
213 | var SubPixelAntiAliased string
214 |
215 | var PixelArtShader = ShaderConfig{
216 | VertexShader: PixelArtVert,
217 | FragmentShader: SubPixelAntiAliased,
218 | // FragmentShader: SubPixelAntiAliased,
219 | VertexFormat: VertexFormat{
220 | VertexAttribute("positionIn", AttrVec3, PositionXYZ),
221 | VertexAttribute("colorIn", AttrVec4, ColorRGBA),
222 | VertexAttribute("texCoordIn", AttrVec2, TexCoordXY),
223 | },
224 | UniformFormat: UniformFormat{
225 | Attr{"model", AttrMat4},
226 | Attr{"projection", AttrMat4},
227 | Attr{"view", AttrMat4},
228 | Attr{"texelsPerPixel", AttrFloat},
229 | },
230 | }
231 |
232 | //go:embed pixel.vs
233 | var PixelArtVert string
234 |
235 | //go:embed pixel.fs
236 | var PixelArtFrag string
237 |
238 | var PixelArtShader2 = ShaderConfig{
239 | VertexShader: PixelArtVert,
240 | FragmentShader: PixelArtFrag,
241 | VertexFormat: VertexFormat{
242 | VertexAttribute("positionIn", AttrVec3, PositionXYZ),
243 | VertexAttribute("colorIn", AttrVec4, ColorRGBA),
244 | VertexAttribute("texCoordIn", AttrVec2, TexCoordXY),
245 | },
246 | UniformFormat: UniformFormat{
247 | Attr{"model", AttrMat4},
248 | Attr{"projection", AttrMat4},
249 | Attr{"view", AttrMat4},
250 | },
251 | }
252 |
253 | //go:embed mesh.vs
254 | var DiffuseVertexShader string
255 |
256 | //go:embed flat.fs
257 | var DiffuseFragmentShader string
258 |
259 | var DiffuseShader = ShaderConfig{
260 | VertexShader: DiffuseVertexShader,
261 | FragmentShader: DiffuseFragmentShader,
262 | VertexFormat: VertexFormat{
263 | VertexAttribute("positionIn", AttrVec3, PositionXYZ),
264 | VertexAttribute("normalIn", AttrVec3, NormalXYZ),
265 | // VertexAttribute("colorIn", AttrVec4, ColorRGBA),
266 | VertexAttribute("texCoordIn", AttrVec2, TexCoordXY),
267 | },
268 | UniformFormat: UniformFormat{
269 | Attr{"model", AttrMat4},
270 | Attr{"view", AttrMat4},
271 | Attr{"projection", AttrMat4},
272 |
273 | Attr{"viewPos", AttrVec3},
274 |
275 | Attr{"material.ambient", AttrVec3},
276 | Attr{"material.diffuse", AttrVec3},
277 | Attr{"material.specular", AttrVec3},
278 | Attr{"material.shininess", AttrFloat},
279 |
280 | Attr{"dirLight.direction", AttrVec3},
281 | Attr{"dirLight.ambient", AttrVec3},
282 | Attr{"dirLight.diffuse", AttrVec3},
283 | Attr{"dirLight.specular", AttrVec3},
284 | },
285 | }
286 |
287 | //go:embed sprite-repeat.fs
288 | var SpriteRepeatFragmentShader string
289 |
290 | var SpriteRepeatShader = ShaderConfig{
291 | VertexShader: SpriteVertexShader,
292 | FragmentShader: SpriteRepeatFragmentShader,
293 | VertexFormat: VertexFormat{
294 | VertexAttribute("positionIn", AttrVec3, PositionXYZ),
295 | VertexAttribute("colorIn", AttrVec4, ColorRGBA),
296 | VertexAttribute("texCoordIn", AttrVec2, TexCoordXY),
297 | },
298 | UniformFormat: UniformFormat{
299 | Attr{"model", AttrMat4},
300 | Attr{"projection", AttrMat4},
301 | Attr{"view", AttrMat4},
302 | Attr{"iTime", AttrFloat},
303 | Attr{"zoom", AttrVec2},
304 | Attr{"repeatRect", AttrVec4},
305 | },
306 | }
307 |
308 |
309 | //go:embed sprite-water.fs
310 | var SpriteWaterFragmentShader string
311 |
312 | var SpriteWaterShader = ShaderConfig{
313 | VertexShader: SpriteVertexShader,
314 | FragmentShader: SpriteWaterFragmentShader,
315 | VertexFormat: VertexFormat{
316 | VertexAttribute("positionIn", AttrVec3, PositionXYZ),
317 | VertexAttribute("colorIn", AttrVec4, ColorRGBA),
318 | VertexAttribute("texCoordIn", AttrVec2, TexCoordXY),
319 | },
320 | UniformFormat: UniformFormat{
321 | Attr{"model", AttrMat4},
322 | Attr{"projection", AttrMat4},
323 | Attr{"view", AttrMat4},
324 | Attr{"iTime", AttrFloat},
325 | Attr{"zoom", AttrVec2},
326 | Attr{"repeatRect", AttrVec4},
327 | Attr{"scaleVal", AttrFloat},
328 | Attr{"scaleFreq", AttrFloat},
329 | Attr{"noiseMoveBias", AttrVec2},
330 | Attr{"moveBias", AttrVec2},
331 | },
332 | }
333 |
--------------------------------------------------------------------------------
/shaders/sprite-repeat.fs:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | // Required for webgl
4 | #ifdef GL_ES
5 | precision highp float;
6 | #endif
7 |
8 | out vec4 FragColor;
9 |
10 | in vec4 ourColor;
11 | in vec2 TexCoord;
12 |
13 | uniform float iTime;
14 | uniform vec2 zoom;
15 | uniform vec4 repeatRect; // Note: This is in UV space
16 |
17 | //texture samplers
18 | uniform sampler2D texture1;
19 |
20 | void main()
21 | {
22 | // Repeat
23 | vec2 rMin = vec2(repeatRect.x, repeatRect.y);
24 | vec2 rMax = vec2(repeatRect.z, repeatRect.w);
25 | vec2 rSize = rMax - rMin;
26 | vec2 rCoord = rMin + mod(TexCoord * zoom, rSize);
27 |
28 | vec4 tex = texture(texture1, rCoord);
29 | if (tex.a == 0.0) {
30 | discard;
31 | }
32 |
33 | FragColor = ourColor * tex;
34 | }
35 |
--------------------------------------------------------------------------------
/shaders/sprite-water.fs:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | // Required for webgl
4 | #ifdef GL_ES
5 | precision highp float;
6 | #endif
7 |
8 | out vec4 FragColor;
9 |
10 | in vec4 ourColor;
11 | in vec2 TexCoord;
12 |
13 | uniform float iTime;
14 | uniform vec2 zoom;
15 | uniform vec4 repeatRect; // Note: This is in UV space
16 | uniform float scaleVal;
17 | uniform float scaleFreq;
18 | uniform vec2 moveBias;
19 | uniform vec2 noiseMoveBias;
20 |
21 | //texture samplers
22 | uniform sampler2D texture1;
23 |
24 | vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }
25 |
26 | float snoise(vec2 v){
27 | const vec4 C = vec4(0.211324865405187, 0.366025403784439,
28 | -0.577350269189626, 0.024390243902439);
29 | vec2 i = floor(v + dot(v, C.yy) );
30 | vec2 x0 = v - i + dot(i, C.xx);
31 | vec2 i1;
32 | i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
33 | vec4 x12 = x0.xyxy + C.xxzz;
34 | x12.xy -= i1;
35 | i = mod(i, 289.0);
36 | vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
37 | + i.x + vec3(0.0, i1.x, 1.0 ));
38 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy),
39 | dot(x12.zw,x12.zw)), 0.0);
40 | m = m*m ;
41 | m = m*m ;
42 | vec3 x = 2.0 * fract(p * C.www) - 1.0;
43 | vec3 h = abs(x) - 0.5;
44 | vec3 ox = floor(x + 0.5);
45 | vec3 a0 = x - ox;
46 | m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
47 | vec3 g;
48 | g.x = a0.x * x0.x + h.x * x0.y;
49 | g.yz = a0.yz * x12.xz + h.yz * x12.yw;
50 | return 130.0 * dot(m, g);
51 | }
52 |
53 | void main()
54 | {
55 | vec2 tc = TexCoord + (noiseMoveBias * iTime); // Add Movement
56 | float v = (snoise(tc * scaleFreq) * scaleVal); // Sample noise value
57 |
58 | vec2 texOffset = moveBias * iTime;
59 |
60 | vec2 tCoord = TexCoord + (v - (scaleVal / 2.0)) + texOffset;
61 |
62 | // Repeat
63 | vec2 rMin = vec2(repeatRect.x, repeatRect.y);
64 | vec2 rMax = vec2(repeatRect.z, repeatRect.w);
65 | vec2 rSize = rMax - rMin;
66 | vec2 rCoord = rMin + mod(tCoord * zoom, rSize);
67 |
68 |
69 | vec4 tex = texture(texture1, rCoord);
70 | if (tex.a == 0.0) {
71 | discard;
72 | }
73 |
74 | FragColor = ourColor * tex;
75 |
76 | // --------------------------------------------------------------------------------
77 |
78 | /* vec2 tc = TexCoord + (noiseMoveBias* iTime); */
79 | /* float v = (snoise(tc * scaleFreq) * scaleVal); */
80 | /* FragColor = vec4(v, v, v, 1.0); */
81 | }
82 |
--------------------------------------------------------------------------------
/shaders/sprite.fs:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | // Required for webgl
4 | #ifdef GL_ES
5 | precision highp float;
6 | #endif
7 |
8 | out vec4 FragColor;
9 |
10 | in vec4 ourColor;
11 | in vec2 TexCoord;
12 |
13 | //texture samplers
14 | uniform sampler2D texture1;
15 |
16 | /* // 0 -> texture * color */
17 | /* // Linear interp in between */
18 | /* // 1 -> color */
19 | /* uniform float silhouetteMix; */
20 |
21 | void main()
22 | {
23 | vec4 tex = texture(texture1, TexCoord);
24 | if (tex.a == 0.0) {
25 | discard;
26 | }
27 |
28 | /* // linearly interpolate between both textures (80% container, 20% awesomeface) */
29 | /* //FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2); */
30 | /* FragColor = ourColor * tex; */
31 | /* // FragColor = vec4(ourColor, 1.0) * texture(texture1, TexCoord); */
32 | /* // FragColor = vec4(ourColor, 1.0); */
33 |
34 | /* vec4 spriteColor = ourColor * tex; */
35 | /* vec4 silhouetteColor = ourColor; */
36 | /* FragColor = mix(spriteColor, silhouetteColor, silhouetteMix); */
37 |
38 |
39 | // If alpha > 1.1 then switch to silhuette mode
40 | if (ourColor.a > 1.1) {
41 | FragColor = ourColor;
42 | } else {
43 | FragColor = ourColor * tex;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/shaders/sprite.vs:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | layout (location = 0) in vec3 positionIn;
4 | layout (location = 1) in vec4 colorIn;
5 | layout (location = 2) in vec2 texCoordIn;
6 |
7 | out vec4 ourColor;
8 | out vec2 TexCoord;
9 |
10 | uniform mat4 model;
11 | uniform mat4 projection;
12 | uniform mat4 view;
13 | //uniform mat4 transform;
14 |
15 | void main()
16 | {
17 | gl_Position = projection * view * model * vec4(positionIn, 1.0);
18 | /* // Snap pixels */
19 | /* vec2 pos = vec2(round(positionIn.x), round(positionIn.y)); */
20 |
21 | /* // Apply camera */
22 | /* gl_Position = projection * view * vec4(positionIn, 1.0); */
23 |
24 | ourColor = colorIn;
25 |
26 | TexCoord = vec2(texCoordIn.x, texCoordIn.y);
27 | }
28 |
--------------------------------------------------------------------------------
/shaders/sprite_100.fs:
--------------------------------------------------------------------------------
1 | #version 100
2 | // Spec: https://registry.khronos.org/OpenGL/specs/es/2.0/GLSL_ES_Specification_1.00.pdf
3 |
4 | // Required for webgl - TODO - is this needed for version 100?
5 | precision highp float;
6 |
7 | varying vec4 ourColor;
8 | varying vec2 TexCoord;
9 |
10 | //vec4 FragColor;
11 |
12 | //texture samplers
13 | uniform sampler2D texture1;
14 |
15 | void main()
16 | {
17 | vec4 tex = texture2D(texture1, TexCoord);
18 | if (tex.a == 0.0) {
19 | discard;
20 | }
21 | // linearly interpolate between both textures (80% container, 20% awesomeface)
22 | //FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
23 | gl_FragColor = ourColor * tex;
24 | // FragColor = vec4(ourColor, 1.0) * texture(texture1, TexCoord);
25 | // FragColor = vec4(ourColor, 1.0);
26 | }
27 |
--------------------------------------------------------------------------------
/shaders/sprite_100.vs:
--------------------------------------------------------------------------------
1 | #version 100
2 | // Spec: https://registry.khronos.org/OpenGL/specs/es/2.0/GLSL_ES_Specification_1.00.pdf
3 |
4 | attribute vec2 positionIn;
5 | attribute vec4 colorIn;
6 | attribute vec2 texCoordIn;
7 |
8 | varying vec4 ourColor;
9 | varying vec2 TexCoord;
10 |
11 | // uniform mat4 model;
12 | uniform mat4 projection;
13 | uniform mat4 view;
14 | //uniform mat4 transform;
15 |
16 | void main()
17 | {
18 | gl_Position = projection * view * vec4(positionIn, 0.0, 1.0);
19 | // gl_Position = projection * transform * vec4(aPos, 1.0);
20 | // gl_Position = vec4(aPos, 1.0);
21 | ourColor = colorIn;
22 |
23 | TexCoord = vec2(texCoordIn.x, texCoordIn.y);
24 | }
25 |
--------------------------------------------------------------------------------
/shaders/subPixel.fs:
--------------------------------------------------------------------------------
1 | #version 300 es
2 | // Required for webgl
3 | #ifdef GL_ES
4 | precision highp float;
5 | #endif
6 |
7 | out vec4 FragColor;
8 |
9 | in vec4 ourColor;
10 | in vec2 TexCoord;
11 |
12 | // View matrix uniform
13 | uniform mat4 view;
14 |
15 | // This is essentially the camera zoom level
16 | // E.G. If you were zoomed in 4x, you'd pass in 1.0/4.0 (ie 1.0 texture texel maps to 4.0 screen pixels)
17 | uniform float texelsPerPixel;
18 |
19 | //texture samplers
20 | uniform sampler2D texture1;
21 |
22 | void main()
23 | {
24 | // --- For Pixel art games ---
25 | // TODO - This isn't working, I'm not sure why
26 | // Note: Ensure you enable Linear filtering (ie smooth) on textures
27 | // https://www.youtube.com/watch?v=Yu8k7a1hQuU - Pixel art rendering
28 | // Adapted from: https://www.shadertoy.com/view/MlB3D3
29 | // More reading: https://jorenjoestar.github.io/post/pixel_art_filtering/
30 |
31 | // Attempt 1 - This looks blurry
32 | /* vec2 textureSize2d = vec2(textureSize(texture1, 0)); */
33 | /* /\* vec2 textureSize2d = vec2(1024, 1024); *\/ */
34 | /* vec2 pixel = TexCoord * textureSize2d.xy; */
35 |
36 | /* // Calculate the scale of the view matrix (used for scaling the subpixels) */
37 | /* /\* vec2 scale = vec2(view[3][0]/4.0, view[3][1]/4.0); *\/ */
38 | /* /\* float scale = 1.0; *\/ */
39 | /* vec2 scale = vec2(length(vec3(view[0][0], view[0][1], view[0][2])), length(vec3(view[1][0], view[1][1], view[1][2]))); */
40 |
41 | /* /\* vec2 scale = vec2(5.0 * view[0][0], 5.0 * view[0][0]); *\/ */
42 | /* /\* vec2 scale = vec2(10.0 * view[0][0], 10.0 * view[0][0]); *\/ */
43 |
44 |
45 | /* /\* scale = scale * 0.5; // TODO - Magic number, this just seems to look good *\/ */
46 |
47 | /* // emulate point sampling */
48 | /* vec2 uv = floor(pixel) + 0.5; */
49 | /* // subpixel aa algorithm (COMMENT OUT TO COMPARE WITH POINT SAMPLING) */
50 | /* // TODO - This is shimmering, I'm not sure why, I think the scale is wrong */
51 | /* uv += 1.0 - clamp((1.0 - fract(pixel)) * scale, 0.0, 1.0); */
52 |
53 | /* // output */
54 | /* vec4 color = texture(texture1, uv / textureSize2d.xy); */
55 |
56 | /* // -------------------------------------------------------------------------------- */
57 | /* // Attempt 2: https://colececil.io/blog/2017/scaling-pixel-art-without-destroying-it/ */
58 | /* // -------------------------------------------------------------------------------- */
59 | /* // Note: This looks less blurry */
60 | /* /\* float texelsPerPixel = 100.0; // TODO - Not sure where to get this from *\/ */
61 | /* /\* float texelsPerPixel = 1.0 / 8.0; *\/ */
62 | /* /\* float texelsPerPixel = 1.0 / 4.0; // (1920.0 / 4.0))/ 1920.0; // TODO: pass in with uniform *\/ */
63 | /* float texelsPerPixel = 1.0 / 8.0; */
64 | /* float texelsPerPixel = 1.0 / view[0][0]; */
65 | /* float texelsPerPixel = view[0][0]; */
66 |
67 | vec2 textureSize2d = vec2(textureSize(texture1, 0));
68 | vec2 pixel = TexCoord * textureSize2d.xy;
69 |
70 | vec2 locationWithinTexel = fract(pixel);
71 | vec2 interpolationAmount = clamp(locationWithinTexel / texelsPerPixel, 0.0, 0.5) + clamp((locationWithinTexel - 1.0) / texelsPerPixel + 0.5, 0.0, 0.5);
72 | /* vec2 interpolationAmount = clamp(locationWithinTexel * texelsPerPixel, 0.0, 0.5) + clamp((locationWithinTexel - 1.0) * texelsPerPixel + 0.5, 0.0, 0.5); */
73 | vec2 finalTextureCoords = (floor(pixel) + interpolationAmount) / textureSize2d.xy;
74 |
75 | // output
76 | vec4 color = texture(texture1, finalTextureCoords);
77 |
78 | // --------------------------------------------------------------------------------
79 | // https://www.shadertoy.com/view/MlB3D3
80 | // --------------------------------------------------------------------------------
81 | /* vec2 textureSize2d = vec2(textureSize(texture1, 0)); */
82 | /* vec2 pix = (TexCoord * textureSize2d.xy) * texelsPerPixel; */
83 |
84 | /* pix = floor(pix) + min(fract(pix) / fwidth(pix), 1.0) - 0.5; */
85 | /* /\* pix = floor(pix) + smoothstep(0.0, 1.0, fract(pix) / fwidth(pix)) - 0.5; // Sharper *\/ */
86 |
87 | /* // sample and return */
88 | /* vec4 color = texture(texture1, pix / textureSize2d.xy); */
89 |
90 | // Attempt 2
91 | /* vec2 textureSize2d = vec2(textureSize(texture1, 0)); */
92 | /* vec2 pix = (TexCoord * textureSize2d.xy); */
93 |
94 | /* /\* pix = floor(pix) + min(fract(pix) / fwidth(pix), 1.0) - 0.5; *\/ */
95 | /* pix = floor(pix) + smoothstep(0.0, 1.0, fract(pix) / fwidth(pix)) - 0.5; // Sharper */
96 |
97 | /* // sample and return */
98 | /* vec4 color = texture(texture1, pix / textureSize2d.xy); */
99 |
100 |
101 | //--------------------------------------------------------------------------------
102 | // https://jorenjoestar.github.io/post/pixel_art_filtering/
103 | //--------------------------------------------------------------------------------
104 | /* // Nearest */
105 | /* vec2 textureSize2d = vec2(textureSize(texture1, 0)); */
106 | /* vec2 pixel = TexCoord * textureSize2d; */
107 | /* pixel = floor(pixel) + 0.5; */
108 | /* vec4 color = texture(texture1, pixel / textureSize2d.xy); */
109 |
110 | /* // CSantos */
111 | /* vec2 textureSize2d = vec2(textureSize(texture1, 0)); */
112 | /* vec2 pixel = TexCoord * textureSize2d; */
113 | /* vec2 alpha = 0.7 * fwidth(pixel); */
114 | /* vec2 pixel_fract = fract(pixel); */
115 | /* vec2 pixel_diff1 = clamp(0.5 / alpha * pixel_fract, 0.0, 0.5); */
116 | /* vec2 pfSub1 = pixel_fract - vec2(1, 1); */
117 | /* vec2 pixel_diff2 = clamp(0.5 / alpha * (pfSub1) + 0.5, 0.0, 0.5); */
118 | /* vec2 pixel_diff = pixel_diff1 + pixel_diff2; */
119 | /* pixel = floor(pixel) + pixel_diff; */
120 | /* vec4 color = texture(texture1, pixel / textureSize2d.xy); */
121 |
122 | /* // FatPixel */
123 | /* vec2 textureSize2d = vec2(textureSize(texture1, 0)); */
124 | /* vec2 pixel = TexCoord * textureSize2d; */
125 | /* vec2 fat_pixel = floor(pixel) + 0.5; */
126 | /* fat_pixel += vec2(1, 1) - clamp((vec2(1, 1) - fract(pixel)) * texelsPerPixel, 0.0, 1.0); */
127 | /* vec4 color = texture(texture1, pixel / textureSize2d.xy); */
128 |
129 | if (color.a == 0.0) {
130 | discard;
131 | }
132 |
133 | FragColor = ourColor * color;
134 | }
135 |
--------------------------------------------------------------------------------
/sorter.go:
--------------------------------------------------------------------------------
1 | package glitch
2 |
3 | import (
4 | "cmp"
5 | "slices"
6 | )
7 |
8 | // you were here creating the sorter
9 | // 1. every draw command needs ALL of the data to be sorted
10 | // 2. you need to simplify the sorting to its all command based imo
11 | type Sorter struct {
12 | DepthTest bool
13 | SoftwareSort SoftwareSortMode
14 | DepthBump bool
15 | depthBump float32
16 | currentLayer int8
17 |
18 | // States that are used for forming the draw command
19 | // blendMode BlendMode
20 | // shader *Shader
21 | // material Material
22 |
23 | // camera *CameraOrtho
24 |
25 | commands []cmdList
26 | }
27 |
28 | func NewSorter() *Sorter {
29 | return &Sorter{
30 | commands: make([]cmdList, 256), // TODO - hardcoding from sizeof(uint8)
31 | }
32 | }
33 |
34 | func (s *Sorter) SetLayer(layer int8) {
35 | s.currentLayer = layer
36 | }
37 |
38 | func (s *Sorter) Layer() int8 {
39 | return s.currentLayer
40 | }
41 |
42 | func (s *Sorter) Clear() {
43 | s.depthBump = 0
44 |
45 | // Clear stuff
46 | for l := range s.commands {
47 | s.commands[l].Clear()
48 | }
49 | }
50 |
51 | // func (s *Sorter) SetShader(shader *Shader) {
52 | // s.shader = shader
53 | // }
54 |
55 | // func (s *Sorter) SetMaterial(material Material) {
56 | // s.material = material
57 | // }
58 |
59 | // func (s *Sorter) SetCamera(camera *CameraOrtho) {
60 | // s.camera = camera
61 | // }
62 |
63 | func (s *Sorter) Draw(target BatchTarget) {
64 | s.sort()
65 |
66 | if s.DepthTest {
67 | // Opaque goes front to back (0 to 255)
68 | for l := range s.commands {
69 | for i := range s.commands[l].Opaque {
70 | // fmt.Println("- Opaque: (layer, x, z)", l, s.commands[l].Opaque[i].matrix[i4_3_1], s.commands[l].Opaque[i].matrix[i4_3_2])
71 | s.applyDrawCommand(target, s.commands[l].Opaque[i])
72 | }
73 | }
74 |
75 | // Translucent goes from back to front (255 to 0)
76 | for l := len(s.commands) - 1; l >= 0; l-- { // Reverse order so that layer 0 is drawn last
77 | for i := range s.commands[l].Translucent {
78 | // for i := len(s.commands[l].Translucent)-1; i >= 0; i-- {
79 | // fmt.Println("- Transl: (layer, x, z)", l, s.commands[l].Translucent[i].matrix[i4_3_1], s.commands[l].Translucent[i].matrix[i4_3_2])
80 | s.applyDrawCommand(target, s.commands[l].Translucent[i])
81 | }
82 | }
83 | } else {
84 | for l := len(s.commands) - 1; l >= 0; l-- { // Reverse order so that layer 0 is drawn last
85 | for i := range s.commands[l].Opaque {
86 | s.applyDrawCommand(target, s.commands[l].Opaque[i])
87 | }
88 | for i := range s.commands[l].Translucent {
89 | s.applyDrawCommand(target, s.commands[l].Translucent[i])
90 | }
91 | }
92 | }
93 |
94 | s.Clear()
95 | }
96 |
97 | func (s *Sorter) applyDrawCommand(target BatchTarget, c drawCommand) {
98 | target.Add(c.filler, c.matrix, c.mask, c.material)
99 | }
100 |
101 | func (s *Sorter) Add(filler GeometryFiller, mat glMat4, mask RGBA, material Material) {
102 | if mask.A == 0 {
103 | return
104 | } // discard b/c its completely transparent
105 |
106 | // TODO: Slightly hacky but basically if the sprite expects to blend then we want to sort it as if it translucent
107 | translucent := false
108 | if mask.A != 1 {
109 | translucent = true
110 | if material.blend == BlendModeNone {
111 | // TODO: Hacky, correct the blend mode if its translucent
112 | material.SetBlendMode(BlendModeNormal)
113 | }
114 | }
115 |
116 | if material.blend != BlendModeNone {
117 | translucent = true
118 | }
119 |
120 | if s.DepthTest {
121 | // If we are doing depth testing, then use the s.CurrentLayer field to determine the depth (normalizing from (0 to 1). Notably the standard ortho cam is (-1, 1) which this range fits into but is easier to normalize to // TODO - make that depth range tweakable?
122 | // TODO - hardcoded because layer is a uint8. You probably want to make layer an int and then just set depth based on that
123 | // depth := 1 - (float32(s.currentLayer) / float32(math.MaxUint8))
124 | // // fmt.Println("Old/New: ", mat[i4_3_2], depth)
125 | // mat[i4_3_2] = depth // Set Z translation to the depth
126 |
127 | // fmt.Println("Apply: ", mat.Apply(Vec3{0, 0, 0}))
128 |
129 | // mat[i4_3_2] = mat[i4_3_1] // Set Z translation to the y point
130 |
131 | // Add the layer to the depth
132 | if s.DepthBump {
133 | s.depthBump -= 0.00001 // TODO: Very very arbitrary
134 | }
135 | mat[i4_3_2] -= float32(s.currentLayer) + s.depthBump
136 | // fmt.Println("Depth: ", mat[i4_3_2])
137 |
138 | // TODO: Hardcoded depthmode sort type
139 | material.depth = DepthModeLess
140 | } else {
141 | // TODO: If depthtest if false, I want to ensure that the drawCalls are sorted as if they are translucent
142 | translucent = true
143 | mat[i4_3_2] -= float32(s.currentLayer)
144 | }
145 |
146 | // state := BufferState{materialGroup{s.material, material}, s.blendMode}
147 | // // if s.camera != nil {
148 | // // material.camera = s.camera
149 | // // }
150 | // if s.DepthTest {
151 | // material.depth = DepthModeLess
152 | // }
153 |
154 | s.commands[s.currentLayer].Add(translucent, drawCommand{
155 | filler, mat, mask, material,
156 | })
157 | }
158 |
159 | func (s *Sorter) sort() {
160 | if s.DepthTest {
161 | // TODO - do special sort function for depth test code:
162 | // 1. Fully Opaque or fully transparent groups of meshes: Don't sort inside that group
163 | // 2. Partially transparent groups of meshes: sort inside that group
164 | // 3. Take into account blendMode
165 |
166 | // Sort translucent buffer
167 | for l := range s.commands {
168 | s.commands[l].SortTranslucent(s.SoftwareSort)
169 | }
170 |
171 | return
172 | }
173 |
174 | // Sort both buffers
175 | for l := range s.commands {
176 | s.commands[l].SortTranslucent(s.SoftwareSort)
177 | s.commands[l].SortOpaque(s.SoftwareSort)
178 | }
179 | }
180 |
181 | // --------------------------------------------------------------------------------
182 | type cmdList struct {
183 | Opaque []drawCommand // TODO: This is kindof more like the unsorted list
184 | Translucent []drawCommand // TODO: This is kindof more like the sorted list
185 |
186 | }
187 |
188 | func (c *cmdList) Add(translucent bool, cmd drawCommand) {
189 | if translucent {
190 | c.Translucent = append(c.Translucent, cmd)
191 | // return &c.Translucent[len(c.Translucent)-1]
192 | } else {
193 | c.Opaque = append(c.Opaque, cmd)
194 | // return &c.Opaque[len(c.Opaque)-1]
195 | }
196 | }
197 |
198 | func (c *cmdList) SortTranslucent(sortMode SoftwareSortMode) {
199 | SortDrawCommands(c.Translucent, sortMode)
200 | }
201 |
202 | func (c *cmdList) SortOpaque(sortMode SoftwareSortMode) {
203 | SortDrawCommands(c.Opaque, sortMode)
204 | }
205 |
206 | func (c *cmdList) Clear() {
207 | c.Opaque = c.Opaque[:0]
208 | c.Translucent = c.Translucent[:0]
209 | }
210 |
211 | type SoftwareSortMode uint8
212 |
213 | const (
214 | SoftwareSortNone SoftwareSortMode = iota
215 | SoftwareSortX // Sort based on the X position
216 | SoftwareSortY // Sort based on the Y position
217 | SoftwareSortZ // Sort based on the Z position
218 | SoftwareSortZNegative // Opposite order to SoftwareSortZ
219 | SoftwareSortCommand // Sort by the computed drawCommand.command
220 | )
221 |
222 | type drawCommand struct {
223 | filler GeometryFiller
224 | matrix glMat4
225 | mask RGBA
226 | material Material
227 | }
228 |
229 | func SortDrawCommands(buf []drawCommand, sortMode SoftwareSortMode) {
230 | if sortMode == SoftwareSortNone {
231 | return
232 | } // Skip if sorting disabled
233 |
234 | if sortMode == SoftwareSortX {
235 | slices.SortStableFunc(buf, func(a, b drawCommand) int {
236 | return -cmp.Compare(a.matrix[i4_3_0], b.matrix[i4_3_0]) // sort by x
237 | })
238 | } else if sortMode == SoftwareSortY {
239 | slices.SortStableFunc(buf, func(a, b drawCommand) int {
240 | return -cmp.Compare(a.matrix[i4_3_1], b.matrix[i4_3_1]) // sort by y
241 | })
242 | } else if sortMode == SoftwareSortZ {
243 | slices.SortStableFunc(buf, func(a, b drawCommand) int {
244 | return cmp.Compare(a.matrix[i4_3_2], b.matrix[i4_3_2]) // sort by z
245 | })
246 | } else if sortMode == SoftwareSortZNegative {
247 | slices.SortStableFunc(buf, func(a, b drawCommand) int {
248 | return -cmp.Compare(a.matrix[i4_3_2], b.matrix[i4_3_2]) // sort by z
249 | })
250 | } // else if sortMode == SoftwareSortCommand {
251 | // slices.SortStableFunc(buf, func(a, b drawCommand) int {
252 | // return -cmp.Compare(a.command, b.command) // sort by command
253 | // })
254 | // }
255 | }
256 |
--------------------------------------------------------------------------------
/state.go:
--------------------------------------------------------------------------------
1 | package glitch
2 |
3 | // TODO - maybe push this up into internal/gl?
4 | // TODO - this might lock us up into a single window? That doesn't seem like too bad of a requirement though
5 |
6 | import (
7 | "github.com/unitoftime/glitch/internal/gl"
8 | "github.com/unitoftime/glitch/internal/mainthread"
9 | )
10 |
11 | type stateTracker struct {
12 | // FBO
13 | fbo gl.Framebuffer
14 | fboBounds Rect
15 | fboBinder func()
16 |
17 | // Depth Test
18 | // depthTest bool
19 | // enableDepthFunc func()
20 | // depthFunc gl.Enum
21 | // depthFuncBinder func()
22 | depthMode DepthMode
23 | depthModeBinder func()
24 |
25 | // Texture
26 | texture *Texture
27 | textureBinder func()
28 |
29 | // BlendFunc
30 | // blendSrc, blendDst gl.Enum
31 | blendMode BlendMode
32 | blendModeBinder func()
33 |
34 | // Cull Mode
35 | // cullModeEnable bool
36 | cullMode CullMode
37 | cullModeBinder func()
38 |
39 | // Vert Buffer
40 | vertBuf *VertexBuffer
41 | vertBufDrawer func()
42 |
43 | clearColor RGBA
44 | clearMode gl.Enum
45 | clearFunc func()
46 | }
47 |
48 | var state *stateTracker
49 |
50 | func init() {
51 | state = &stateTracker{}
52 |
53 | state.depthModeBinder = func() {
54 | if state.depthMode == DepthModeNone {
55 | gl.Disable(gl.DEPTH_TEST)
56 | } else {
57 | gl.Enable(gl.DEPTH_TEST)
58 | data := depthModeLut[state.depthMode]
59 | gl.DepthFunc(data.mode)
60 | }
61 | }
62 |
63 | state.blendModeBinder = func() {
64 | if state.blendMode == BlendModeNone {
65 | gl.Disable(gl.BLEND)
66 | return
67 | }
68 |
69 | gl.Enable(gl.BLEND) // TODO: This only needs to run if it was disabled previously
70 |
71 | data := blendModeLut[state.blendMode]
72 | gl.BlendFunc(data.src, data.dst)
73 | }
74 |
75 | state.cullModeBinder = func() {
76 | if state.cullMode == CullModeNone {
77 | gl.Disable(gl.CULL_FACE)
78 | } else {
79 | gl.Enable(gl.CULL_FACE)
80 | data := cullModeLut[state.cullMode]
81 | gl.CullFace(data.face)
82 | gl.FrontFace(data.dir)
83 | }
84 | }
85 |
86 | // state.enableDepthFunc = func() {
87 | // if state.depthTest {
88 | // gl.Enable(gl.DEPTH_TEST)
89 | // } else {
90 | // gl.Disable(gl.DEPTH_TEST)
91 | // }
92 | // }
93 |
94 | // state.depthFuncBinder = func() {
95 | // gl.DepthFunc(state.depthFunc)
96 | // }
97 |
98 | state.fboBinder = func() {
99 | // TODO - Note: I set the viewport when I bind the framebuffer. Is this okay?
100 | gl.Viewport(0, 0, int(state.fboBounds.W()), int(state.fboBounds.H()))
101 | gl.BindFramebuffer(gl.FRAMEBUFFER, state.fbo)
102 | }
103 |
104 | state.textureBinder = func() {
105 | gl.ActiveTexture(gl.TEXTURE0)
106 | // gl.ActiveTexture(gl.TEXTURE0 + position); // TODO - include position
107 |
108 | if state.texture == nil {
109 | gl.BindTexture(gl.TEXTURE_2D, gl.NoTexture)
110 | } else {
111 | gl.BindTexture(gl.TEXTURE_2D, state.texture.texture)
112 | }
113 | // gl.BindTexture(gl.TEXTURE_2D, state.texture.texture)
114 | }
115 |
116 | // state.blendFuncBinder = func() {
117 | // gl.BlendFunc(state.blendSrc, state.blendDst)
118 | // }
119 |
120 | // state.cullModeBinder = func() {
121 | // if state.cullModeEnable {
122 | // gl.Enable(gl.CULL_FACE)
123 | // gl.CullFace(state.cullMode.face)
124 | // gl.FrontFace(state.cullMode.dir)
125 | // } else {
126 | // gl.Disable(gl.CULL_FACE)
127 | // }
128 | // }
129 |
130 | state.vertBufDrawer = func() {
131 | state.vertBuf.mainthreadDraw()
132 | }
133 |
134 | state.clearFunc = func() {
135 | gl.ClearColor(float32(state.clearColor.R), float32(state.clearColor.G), float32(state.clearColor.B), float32(state.clearColor.A))
136 | // gl.Clear(gl.COLOR_BUFFER_BIT) // Make configurable?
137 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
138 | }
139 | }
140 |
141 | // TODO - maybe allow for more than 15 if the platform supports it? TODO - max texture units
142 | func (s *stateTracker) bindTexture(texture *Texture) {
143 | // TODO: Check if changed
144 | s.texture = texture
145 | mainthread.Call(state.textureBinder)
146 | }
147 |
148 | func (s *stateTracker) bindFramebuffer(fbo gl.Framebuffer, bounds Rect) {
149 | if s.fbo.Equal(fbo) && s.fboBounds == bounds {
150 | return
151 | }
152 | state.fbo = fbo
153 | state.fboBounds = bounds
154 |
155 | mainthread.Call(s.fboBinder)
156 | }
157 |
158 | func (s *stateTracker) setDepthMode(depth DepthMode) {
159 | if s.depthMode == depth {
160 | return // Skip: State already matches
161 | }
162 | s.depthMode = depth
163 |
164 | mainthread.Call(s.depthModeBinder)
165 | }
166 |
167 | func (s *stateTracker) setBlendMode(blend BlendMode) {
168 | if s.blendMode == blend {
169 | return // Skip: State already matches
170 | }
171 | s.blendMode = blend
172 |
173 | mainthread.Call(s.blendModeBinder)
174 | }
175 |
176 | func (s *stateTracker) setCullMode(cull CullMode) {
177 | if s.cullMode == cull {
178 | return // Skip: State already matches
179 | }
180 | s.cullMode = cull
181 |
182 | mainthread.Call(s.cullModeBinder)
183 | }
184 |
185 | // func (s *stateTracker) enableDepthTest(enable bool) {
186 | // if s.depthTest == enable {
187 | // return // Skip if state already matches
188 | // }
189 | // s.depthTest = enable
190 | // mainthread.Call(s.enableDepthFunc)
191 | // }
192 |
193 | // func (s *stateTracker) setDepthFunc(depthFunc gl.Enum) {
194 | // if s.depthFunc == depthFunc {
195 | // return // Skip if already enabled and depth functions match
196 | // }
197 |
198 | // s.depthFunc = depthFunc
199 | // mainthread.Call(s.depthFuncBinder)
200 | // }
201 |
202 | // func (s *stateTracker) setBlendFunc(src, dst gl.Enum) {
203 | // s.blendSrc = src
204 | // s.blendDst = dst
205 | // mainthread.Call(s.blendFuncBinder)
206 | // }
207 |
208 | func (s *stateTracker) drawVertBuffer(vb *VertexBuffer) {
209 | s.vertBuf = vb
210 | mainthread.Call(s.vertBufDrawer)
211 | }
212 |
213 | func (s *stateTracker) clearTarget(color RGBA) {
214 | s.clearColor = color
215 | mainthread.Call(s.clearFunc)
216 | }
217 |
218 | // func (s *stateTracker) disableCullMode() {
219 | // if s.cullModeEnable == false {
220 | // return // Skip if state already matches
221 | // }
222 |
223 | // s.cullModeEnable = false
224 | // mainthread.Call(s.cullModeBinder)
225 | // }
226 |
227 | // func (s *stateTracker) enableCullMode(cullMode CullMode) {
228 | // if s.cullModeEnable == true && s.cullMode == cullMode {
229 | // return // Skip if state already matches
230 | // }
231 |
232 | // s.cullModeEnable = true
233 | // s.cullMode = cullMode
234 | // mainthread.Call(s.cullModeBinder)
235 | // }
236 |
--------------------------------------------------------------------------------
/texture.go:
--------------------------------------------------------------------------------
1 | package glitch
2 |
3 | import (
4 | "image"
5 | "image/color"
6 | "image/draw"
7 | "runtime"
8 |
9 | "github.com/unitoftime/flow/glm"
10 | "github.com/unitoftime/glitch/internal/gl"
11 | "github.com/unitoftime/glitch/internal/mainthread"
12 | )
13 |
14 | // TODO - Should I use this as default? Or is there a way to do null textures for textureless things?
15 | var whiteTexture *Texture
16 |
17 | func WhiteTexture() *Texture {
18 | if whiteTexture != nil {
19 | return whiteTexture
20 | }
21 | max := 128 // TODO - webgl forces textures to be power of 2 - maybe I can go smaller though
22 |
23 | whiteColor := color.RGBA{0xFF, 0xFF, 0xFF, 0xFF}
24 | whiteTexture = NewRGBATexture(max, max, whiteColor, true)
25 | return whiteTexture
26 | }
27 |
28 | func NewRGBATexture(width, height int, col color.RGBA, smooth bool) *Texture {
29 | img := image.NewRGBA(image.Rect(0, 0, width, height))
30 |
31 | for x := 0; x < width; x++ {
32 | for y := 0; y < height; y++ {
33 | img.SetRGBA(x, y, col)
34 | }
35 | }
36 | return NewTexture(img, smooth)
37 | }
38 |
39 | func NewTransparentTexture64() *Texture {
40 | max := 64 // TODO - webgl forces textures to be power of 2 - maybe I can go smaller though
41 | img := image.NewRGBA(image.Rect(0, 0, max, max))
42 |
43 | for x := 0; x < max; x++ {
44 | for y := 0; y < max; y++ {
45 | img.SetRGBA(x, y, color.RGBA{255, 255, 255, 255})
46 | // img.SetRGBA(x,y, color.RGBA{0, 0, 0, 0})
47 | }
48 | }
49 |
50 | return NewTexture(img, true)
51 | }
52 |
53 | type Texture struct {
54 | texture gl.Texture
55 | width, height int
56 | smooth bool
57 | }
58 |
59 | func toRgba(img image.Image) *image.RGBA {
60 | // Short circuit if already RGBA
61 | rgba, isRGBA := img.(*image.RGBA)
62 | if isRGBA {
63 | return rgba
64 | }
65 |
66 | // Else migrate it to the correct pixel format
67 | rgba = image.NewRGBA(img.Bounds())
68 | draw.Draw(rgba, rgba.Bounds(), img, img.Bounds().Min, draw.Src)
69 | return rgba
70 | }
71 |
72 | func NewEmptyTexture(width, height int, smooth bool) *Texture {
73 | t := &Texture{
74 | width: width,
75 | height: height,
76 | smooth: smooth,
77 | }
78 |
79 | t.initialize(nil)
80 | return t
81 | }
82 |
83 | func NewTexture(img image.Image, smooth bool) *Texture {
84 | // We can only use RGBA images right now.
85 | rgba := toRgba(img)
86 |
87 | width := rgba.Bounds().Dx()
88 | height := rgba.Bounds().Dy()
89 | t := &Texture{
90 | width: width,
91 | height: height,
92 | smooth: smooth,
93 | }
94 |
95 | t.initialize(rgba.Pix)
96 |
97 | return t
98 | }
99 |
100 | func (t *Texture) initialize(pixels []uint8) {
101 | mainthread.Call(func() {
102 | t.texture = gl.CreateTexture()
103 | gl.BindTexture(gl.TEXTURE_2D, t.texture)
104 |
105 | gl.TexImage2DFull(gl.TEXTURE_2D, 0, gl.RGBA, t.width, t.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
106 |
107 | // TODO - webgl doesn't support CLAMP_TO_BORDER
108 | // GL_CLAMP_TO_EDGE: The coordinate will simply be clamped between 0 and 1.
109 | // GL_CLAMP_TO_BORDER: The coordinates that fall outside the range will be given a specified border color.
110 |
111 | // TODO - Do I need this all the time? Probably not, especially when I'm doing texture atlases
112 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
113 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
114 |
115 | // gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)
116 | // gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT)
117 |
118 | // TODO - pass smooth in as a parameter
119 | if t.smooth {
120 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
121 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
122 | } else {
123 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
124 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
125 | }
126 | })
127 |
128 | runtime.SetFinalizer(t, (*Texture).delete)
129 | }
130 |
131 | // TODO: This needs to be combined into the NewTexture function, or this needs to bind the texture
132 | func (t *Texture) GenerateMipmap() {
133 | mainthread.Call(func() {
134 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
135 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
136 | gl.GenerateMipmap(gl.TEXTURE_2D)
137 | })
138 | }
139 |
140 | // Sets the texture to be this image.
141 | // Texture size must match img size or this will panic!
142 | // TODO - Should I just try and set it? or do nothing?
143 | func (t *Texture) SetImage(img image.Image) {
144 | if img == nil {
145 | return
146 | }
147 |
148 | if t.width != img.Bounds().Dx() || t.height != img.Bounds().Dy() {
149 | panic("SetImage: img bounds are not equal to texture bounds!")
150 | }
151 |
152 | rgba := toRgba(img)
153 | pixels := rgba.Pix
154 | t.SetPixels(0, 0, t.width, t.height, pixels)
155 | }
156 |
157 | // Sets the pixels of a section of a texture
158 | func (t *Texture) SetPixels(x, y, w, h int, pixels []uint8) {
159 | if len(pixels) != w*h*4 {
160 | panic("set pixels: wrong number of pixels")
161 | }
162 |
163 | // TODO: This is a little inefficient. But I can't messup the global bound texture state
164 | lastTexture := state.texture
165 | defer state.bindTexture(lastTexture)
166 | state.bindTexture(t)
167 |
168 | mainthread.Call(func() {
169 | gl.TexSubImage2D(
170 | gl.TEXTURE_2D,
171 | 0,
172 | x,
173 | y,
174 | w,
175 | h,
176 | gl.RGBA,
177 | gl.UNSIGNED_BYTE,
178 | pixels,
179 | )
180 | })
181 | }
182 |
183 | func (t *Texture) Bounds() Rect {
184 | return glm.R(0, 0, float64(t.width), float64(t.height))
185 | }
186 |
187 | // func (t *Texture) bind(position int) {
188 | // // TODO - maybe allow for more than 15 if the platform supports it? TODO - max texture units
189 | // state.bindTexture(t)
190 | // }
191 |
192 | func (t *Texture) delete() {
193 | mainthread.CallNonBlock(func() {
194 | gl.DeleteTexture(t.texture)
195 | })
196 | }
197 |
--------------------------------------------------------------------------------
/ui/hash.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | "encoding/binary"
5 | "hash/crc64"
6 | "strings"
7 |
8 | "golang.org/x/exp/constraints"
9 | )
10 |
11 | // TODO: You could potententially do an ID stack system: https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-about-the-id-stack-system
12 | func PushId[T constraints.Integer](id T) {
13 | currentIndex := global.stackIndex
14 | global.stackIndex++
15 |
16 | // If not enough room in slice, add a new one
17 | if currentIndex >= len(global.idStack) {
18 | global.idStack = append(global.idStack, make([]byte, 0, 8))
19 | }
20 |
21 | // Convert the data to a byte slice
22 | buf := global.idStack[currentIndex]
23 | buf = buf[:0]
24 | buf = binary.AppendUvarint(buf, uint64(id))
25 |
26 | global.idStack[currentIndex] = buf
27 | }
28 |
29 | func PopId() {
30 | global.stackIndex--
31 | }
32 |
33 | func updateCrcWithIdStack(current uint64) uint64 {
34 | for i := 0; i < global.stackIndex; i++ {
35 | current = bumpCrc(current, global.idStack[i])
36 | }
37 | return current
38 | }
39 |
40 | type eid uint64 // Element Id
41 | const invalidId eid = 0
42 |
43 | var hashTable = crc64.MakeTable(crc64.ISO)
44 |
45 | func crc(label string) uint64 {
46 | return crc64.Checksum([]byte(label), hashTable)
47 | }
48 | func bumpCrc(crc uint64, bump []byte) uint64 {
49 | return crc64.Update(crc, hashTable, bump)
50 | }
51 |
52 | func getIdNoBump(label string) eid {
53 | crc := crc(label)
54 |
55 | id, ok := global.elements[crc]
56 | if !ok {
57 | id = global.idCounter
58 | global.idCounter++
59 | global.elements[crc] = id
60 | // g.elementsRev[id] = label
61 | }
62 |
63 | return id
64 | }
65 |
66 | func getId(label string) eid {
67 | crc := crc(label)
68 | crc = updateCrcWithIdStack(crc)
69 |
70 | bump, alreadyFetched := global.dedup[crc]
71 | if alreadyFetched {
72 | global.dedup[crc] = bump + 1
73 | crc = bumpCrc(crc, []byte{uint8(bump)})
74 | // label = fmt.Sprintf("%s##%d", label, bump)
75 | // fmt.Printf("duplicate label, using bump: %s\n", label)
76 | // panic(fmt.Sprintf("duplicate label found: %s", label))
77 | } else {
78 | global.dedup[crc] = 0
79 | }
80 |
81 | id, ok := global.elements[crc]
82 | if !ok {
83 | id = global.idCounter
84 | global.idCounter++
85 | global.elements[crc] = id
86 | // g.elementsRev[id] = label
87 | }
88 |
89 | return id
90 | }
91 |
92 | func removeDedup(label string) string {
93 | ret, _, _ := strings.Cut(label, "##")
94 | return ret
95 | }
96 |
97 | // Idea 1: To minimize the fmt.Sprintf calls with Ids embedded. you could construct Ids on the fly
98 | // func GetId[T constraints.Ordered](label string, val T) eid {
99 | // crc := crc(label)
100 |
101 | // buf := make([]byte, 0, 8) // Note: causes an alloc
102 | // buf, err := binary.Append(buf, binary.LittleEndian, val)
103 | // if err != nil {
104 | // panic(err)
105 | // }
106 | // crc = bumpCrc(crc, buf)
107 |
108 | // id, ok := global.elements[crc]
109 | // if !ok {
110 | // id = global.idCounter
111 | // global.idCounter++
112 | // global.elements[crc] = id
113 | // // g.elementsRev[id] = label
114 | // }
115 |
116 | // return id
117 | // }
118 |
119 | // func (id eid) TextExt(label string, rect glitch.Rect, textStyle TextStyle) glitch.Rect {
120 | // style := Style{
121 | // Text: textStyle,
122 | // }
123 |
124 | // mask := wmDrawText
125 | // text := removeDedup(label)
126 | // resp := doWidget(id, text, mask, style, rect)
127 | // return resp.textRect
128 | // }
129 |
130 | // func (id eid) ButtonExt(label string, rect glitch.Rect, style Style) bool {
131 | // mask := wmHoverable | wmClickable | wmDrawPanel | wmDrawText
132 | // q text := removeDedup(label)
133 | // resp := doWidget(id, text, mask, style, rect)
134 |
135 | // return resp.Released
136 | // }
137 |
--------------------------------------------------------------------------------
/ui/layout.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import "github.com/unitoftime/glitch"
4 |
5 | // --------------------------------------------------------------------------------
6 | // - Layout
7 | // --------------------------------------------------------------------------------
8 | type LayoutType uint8
9 |
10 | const (
11 | CutLeft LayoutType = iota
12 | CutTop
13 | CutRight
14 | CutBottom
15 | Centered
16 | )
17 |
18 | type SizeType uint8
19 |
20 | const (
21 | // SizeNone SizeType = iota
22 | SizePixels SizeType = iota
23 | SizeText
24 | SizeParent // Percent of parent
25 | SizeChildren
26 | )
27 |
28 | func (s SizeType) Calc(val float64, parentSize float64, textSize float64) float64 {
29 | switch s {
30 | case SizePixels:
31 | return val
32 | case SizeText:
33 | return val * textSize
34 | case SizeParent:
35 | return val * parentSize
36 | }
37 | return 100 // TODO
38 | }
39 |
40 | type Size struct {
41 | TypeX, TypeY SizeType
42 | Value glitch.Vec2
43 |
44 | // Type string // Null, Pixels, TextContent, PercentOfParent, ChildrenSum
45 | // value float32
46 | // strictness float32
47 | }
48 |
49 | type Layout struct {
50 | Type LayoutType
51 | Bounds glitch.Rect
52 | Padding glitch.Rect
53 | Size Size
54 | }
55 |
56 | func (l *Layout) Next(textBounds glitch.Rect) glitch.Rect {
57 | size := l.Size
58 |
59 | // TODO: If percent of parent
60 | // cutX := size.value.X * l.Bounds.W()
61 | // cutY := size.value.Y * l.Bounds.H()
62 | cutX := size.TypeX.Calc(size.Value.X, l.Bounds.W(), textBounds.W())
63 | cutY := size.TypeY.Calc(size.Value.Y, l.Bounds.H(), textBounds.H())
64 |
65 | ret := l.Bounds
66 | switch l.Type {
67 | case CutLeft:
68 | ret = l.Bounds.CutLeft(cutX)
69 | case CutRight:
70 | ret = l.Bounds.CutRight(cutX)
71 | case CutTop:
72 | ret = l.Bounds.CutTop(cutY)
73 | case CutBottom:
74 | ret = l.Bounds.CutBottom(cutY)
75 | case Centered:
76 | ret = ret.SliceHorizontal(cutY).SliceVertical(cutX)
77 | }
78 |
79 | return ret.Unpad(l.Padding)
80 | }
81 |
82 | // --------------------------------------------------------------------------------
83 | type vList struct {
84 | rect glitch.Rect
85 | last glitch.Rect
86 | size float64
87 | padNext float64
88 | fromBottom bool
89 | }
90 |
91 | func (l *vList) Next() glitch.Rect {
92 | if l.fromBottom {
93 | if l.padNext != 0 {
94 | l.rect.CutBottom(l.padNext)
95 | }
96 | l.last = l.rect.CutBottom(l.size)
97 | } else {
98 | if l.padNext != 0 {
99 | l.rect.CutTop(l.padNext)
100 | }
101 | l.last = l.rect.CutTop(l.size)
102 | }
103 | return l.last
104 | }
105 |
106 | func (l vList) Last() glitch.Rect {
107 | return l.last
108 | }
109 |
110 | func VList(rect glitch.Rect, num int) vList {
111 | elementHeight := rect.H() / float64(num)
112 | return vList{
113 | rect: rect,
114 | last: rect,
115 | size: elementHeight,
116 | }
117 | }
118 |
119 | func VList2(rect glitch.Rect, size float64) vList {
120 | return vList{
121 | rect: rect,
122 | last: rect,
123 | size: size,
124 | }
125 | }
126 |
127 | func (l vList) Bottom(val bool) vList {
128 | l.fromBottom = val
129 | return l
130 | }
131 |
132 | func (l vList) Pad(val float64) vList {
133 | l.padNext = val
134 | return l
135 | }
136 |
137 | type hList struct {
138 | rect glitch.Rect
139 | last glitch.Rect
140 | size float64
141 | }
142 |
143 | func HList(rect glitch.Rect, num int) hList {
144 | size := rect.W() / float64(num)
145 | return hList{
146 | rect: rect,
147 | last: rect,
148 | size: size,
149 | }
150 | }
151 | func HList2(rect glitch.Rect, size float64) hList {
152 | return hList{
153 | rect: rect,
154 | last: rect,
155 | size: size,
156 | }
157 | }
158 | func (l *hList) Next() glitch.Rect {
159 | l.last = l.rect.CutLeft(l.size)
160 | return l.last
161 | }
162 |
163 | func (l hList) Last() glitch.Rect {
164 | return l.last
165 | }
166 |
167 | type gridList struct {
168 | rect glitch.Rect
169 | last glitch.Rect
170 | sizeX float64
171 | sizeY float64
172 | numX, numY int
173 | // Type // TODO: RowOrder or ColumnOrder
174 |
175 | currentRect glitch.Rect
176 | currentNum int
177 | }
178 |
179 | func GridList(rect glitch.Rect, numX, numY int) gridList {
180 | sizeX := rect.W() / float64(numX)
181 | sizeY := rect.H() / float64(numY)
182 | return gridList{
183 | rect: rect,
184 | last: rect,
185 | sizeX: sizeX,
186 | sizeY: sizeY,
187 | numX: numX,
188 | numY: numY,
189 | }
190 | }
191 |
192 | func (l *gridList) Next() glitch.Rect {
193 | // Row Order (ie Horizontal first)
194 | if l.currentNum%l.numX == 0 {
195 | l.currentRect = l.rect.CutTop(l.sizeY)
196 | }
197 | l.currentNum++
198 |
199 | l.last = l.currentRect.CutLeft(l.sizeX)
200 |
201 | return l.last
202 | }
203 |
204 | func (l gridList) Last() glitch.Rect {
205 | return l.last
206 | }
207 |
--------------------------------------------------------------------------------
/ui/style.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | "github.com/unitoftime/flow/glm"
5 | "github.com/unitoftime/glitch"
6 | )
7 |
8 | type FullStyle struct {
9 | buttonStyle Style
10 | panelStyle Style
11 | dragSlotStyle Style
12 | dragItemStyle Style
13 |
14 | scrollbarTopStyle Style
15 | scrollbarBotStyle Style
16 | scrollbarHandleStyle Style
17 | scrollbarBgStyle Style
18 |
19 | checkboxStyleTrue Style
20 | checkboxStyleFalse Style
21 |
22 | textInputPanelStyle Style
23 | textCursorStyle Style
24 | tooltipStyle Style
25 |
26 | // textStyle TextStyle // TODO: This should also include the atlas
27 | }
28 |
29 | var gStyle FullStyle
30 |
31 | func SetButtonStyle(style Style) {
32 | gStyle.buttonStyle = style
33 | }
34 | func SetPanelStyle(style Style) {
35 | gStyle.panelStyle = style
36 | }
37 | func SetDragSlotStyle(style Style) {
38 | gStyle.dragSlotStyle = style
39 | }
40 | func DragSlotStyle() Style {
41 | return gStyle.dragSlotStyle
42 | }
43 |
44 | // func SetDragItemStyle(style Style) {
45 | // gStyle.dragItemStyle = style
46 | // }
47 | func DragItemStyle() Style {
48 | return gStyle.dragItemStyle
49 | }
50 | func SetCheckboxStyleTrue(style Style) {
51 | gStyle.checkboxStyleTrue = style
52 | }
53 | func SetCheckboxStyleFalse(style Style) {
54 | gStyle.checkboxStyleFalse = style
55 | }
56 |
57 | // func SetTextStyle(style TextStyle) {
58 | // gStyle.textStyle = style
59 | // }
60 |
61 | func SetTooltipStyle(style Style) {
62 | gStyle.tooltipStyle = style
63 | }
64 |
65 | func SetScrollbarTopStyle(style Style) {
66 | gStyle.scrollbarTopStyle = style
67 | }
68 | func SetScrollbarBottomStyle(style Style) {
69 | gStyle.scrollbarBotStyle = style
70 | }
71 | func SetScrollbarBgStyle(style Style) {
72 | gStyle.scrollbarBgStyle = style
73 | }
74 | func SetScrollbarHandleStyle(style Style) {
75 | gStyle.scrollbarHandleStyle = style
76 | }
77 |
78 | //--------------------------------------------------------------------------------
79 |
80 | type SpriteStyle struct {
81 | sprite Drawer
82 | color glitch.RGBA
83 | rot float64
84 | }
85 |
86 | func NewSpriteStyle(sprite Drawer, color glitch.RGBA) SpriteStyle {
87 | return SpriteStyle{
88 | sprite, color, 0.0,
89 | }
90 | }
91 | func (s SpriteStyle) Sprite(v Drawer) SpriteStyle {
92 | s.sprite = v
93 | return s
94 | }
95 | func (s SpriteStyle) Color(v glitch.RGBA) SpriteStyle {
96 | s.color = v
97 | return s
98 | }
99 | func (s SpriteStyle) Rotation(rot float64) SpriteStyle {
100 | s.rot = rot
101 | return s
102 | }
103 |
104 |
105 | type Style struct {
106 | Normal, Hovered, Pressed SpriteStyle // These are kind of like button states
107 | Text TextStyle
108 | }
109 |
110 | func NewStyle(normal Drawer, color glitch.RGBA) Style {
111 | return Style{
112 | Normal: NewSpriteStyle(normal, color),
113 | Hovered: NewSpriteStyle(normal, color),
114 | Pressed: NewSpriteStyle(normal, color),
115 | Text: NewTextStyle(),
116 | }
117 | }
118 |
119 | func ButtonStyle(normal, hovered, pressed Drawer) Style {
120 | return Style{
121 | Normal: NewSpriteStyle(normal, glitch.White),
122 | Hovered: NewSpriteStyle(hovered, glitch.White),
123 | Pressed: NewSpriteStyle(pressed, glitch.White),
124 | Text: NewTextStyle(),
125 | }
126 | }
127 |
128 | // func (s Style) Normal(v Drawer, c glitch.RGBA) Style {
129 | // s.normal = SpriteStyle{v, c}
130 | // return s
131 | // }
132 | // func (s Style) Hovered(v Drawer, c glitch.RGBA) Style {
133 | // s.hovered = SpriteStyle{v, c}
134 | // return s
135 | // }
136 | // func (s Style) Pressed(v Drawer, c glitch.RGBA) Style {
137 | // s.hovered = SpriteStyle{v, c}
138 | // return s
139 | // }
140 | // func (s Style) Text(v TextStyle) Style {
141 | // s.Text = v
142 | // return s
143 | // }
144 |
145 | // func (s *Style) DrawNormal(group *Group, rect glitch.Rect, color glitch.RGBA) {
146 | // group.draw(s.Normal, rect, color)
147 | // }
148 | // func (s *Style) DrawHot(group *Group, rect glitch.Rect, color glitch.RGBA) {
149 | // group.draw(s.Hovered, rect, color)
150 | // }
151 | // func (s *Style) DrawActive(group *Group, rect glitch.Rect, color glitch.RGBA) {
152 | // group.draw(s.Pressed, rect, color)
153 | // }
154 |
155 | type TextStyle struct {
156 | // TODO: atlas/fontface
157 |
158 | anchor, pivot glitch.Vec2
159 | padding glitch.Rect
160 | color glitch.RGBA
161 | scale float64
162 | autoFit bool // Auto scale the text to fit the rectangle
163 | fitInteger bool // If autoscaling, then only scale by integers (for pixel fonts)
164 | wordWrap bool
165 | shadow glitch.Vec2
166 | }
167 |
168 | // TODO: I kind of feel like the string needs to be in here, I'm not sure though
169 | func NewTextStyle() TextStyle {
170 | return TextStyle{
171 | anchor: glitch.Vec2{0.5, 0.5},
172 | pivot: glitch.Vec2{0.5, 0.5},
173 | padding: glm.R(0, 0, 0, 0),
174 | color: glitch.White,
175 | scale: 1.0,
176 | autoFit: false,
177 | shadow: glitch.Vec2{0.0, 0.0},
178 | }
179 | }
180 |
181 | func (s TextStyle) Anchor(v glitch.Vec2) TextStyle {
182 | s.anchor = v
183 | s.pivot = v
184 | return s
185 | }
186 |
187 | func (s TextStyle) Pivot(v glitch.Vec2) TextStyle {
188 | s.pivot = v
189 | return s
190 | }
191 |
192 | func (s TextStyle) Scale(v float64) TextStyle {
193 | s.scale = v
194 | return s
195 | }
196 |
197 | func (s TextStyle) Padding(v glitch.Rect) TextStyle {
198 | s.padding = v
199 | return s
200 | }
201 | func (s TextStyle) Color(v glitch.RGBA) TextStyle {
202 | s.color = v
203 | return s
204 | }
205 | func (s TextStyle) Autofit(v bool) TextStyle {
206 | s.autoFit = v
207 | if s.autoFit {
208 | s.wordWrap = false
209 | }
210 | return s
211 | }
212 | func (s TextStyle) FitInteger(v bool) TextStyle {
213 | s.fitInteger = v
214 | return s
215 | }
216 |
217 | func (s TextStyle) Shadow(v glitch.Vec2) TextStyle {
218 | s.shadow = v
219 | return s
220 | }
221 | func (s TextStyle) WordWrap(v bool) TextStyle {
222 | s.wordWrap = v
223 | if s.wordWrap {
224 | s.autoFit = false
225 | }
226 | return s
227 | }
228 |
--------------------------------------------------------------------------------