├── .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 | [![Go Reference](https://pkg.go.dev/badge/github.com/unitoftime/glitch.svg)](https://pkg.go.dev/github.com/unitoftime/glitch) 2 | [![build](https://github.com/unitoftime/glitch/actions/workflows/build.yml/badge.svg)](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 | --------------------------------------------------------------------------------