Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,35 @@ Pixel is in development and still missing few critical features. Here're the mos
- Antialiasing (filtering is supported, though)
- ~~Advanced window manipulation (cursor hiding, window icon, ...)~~
- Better support for Hi-DPI displays
- Mobile (and perhaps HTML5?) backend
- Mobile backend (an HTML5/WebGL2 backend ships under `GOOS=js GOARCH=wasm`)
- ~~More advanced graphical effects (e.g. blur)~~ (solved with the addition of GLSL effects)
- Tests and benchmarks
- Vulkan support

**Implementing these features will get us to the 1.0 release.** Contribute, so that it's as soon as
possible!

## WebAssembly (browser)

Pixel ships a WebGL2 backend under the `js && wasm` build tag, so a game
written against the standard `opengl` backend can also be compiled for the
browser:

```sh
GOOS=js GOARCH=wasm go build -o game.wasm ./cmd/game
```

Load `game.wasm` from an HTML page that also serves Go's `wasm_exec.js` and
provides a canvas element. The backend attaches to `<canvas id="game">` by
default; override by setting `opengl.CanvasElementID` before calling
`opengl.NewWindow`.

Some desktop-oriented features are stubbed in the browser: multi-monitor
queries, joysticks/gamepads, and custom cursor images are no-ops, and window
positioning is driven by CSS rather than by `SetPos`. Keyboard, mouse (buttons,
movement, scroll), text input, and resizing (including fullscreen) are wired
up through DOM events.

## Requirements

If you're using Windows and having trouble building Pixel, please check [this guide](./docs/Compilation/Building-Pixel-on-Windows.md) on the [wiki](./docs/README.md).
Expand Down
2 changes: 2 additions & 0 deletions backends/opengl/cursor.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !js

package opengl

import (
Expand Down
31 changes: 31 additions & 0 deletions backends/opengl/cursor_wasm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//go:build js && wasm

package opengl

import (
"image"

"github.com/gopxl/pixel/v2"
)

// StandardCursor matches the desktop-side typed constant. Under WASM we have
// no real cursor API wired up, so the constants exist only so game code can
// reference them unchanged.
type StandardCursor int

const (
ArrowCursor StandardCursor = iota
IBeamCursor
CrosshairCursor
HandCursor
HResizeCursor
VResizeCursor
)

// Cursor is an opaque handle. We keep the struct empty; the browser manages
// the mouse cursor itself.
type Cursor struct{}

func CreateStandardCursor(StandardCursor) *Cursor { return &Cursor{} }
func CreateCursorImage(image.Image, pixel.Vec) *Cursor { return &Cursor{} }
func (w *Window) SetCursor(*Cursor) {}
15 changes: 15 additions & 0 deletions backends/opengl/glpicture.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package opengl

import (
"math"
"sync"

"github.com/gopxl/glhf/v2"
"github.com/gopxl/mainthread/v2"
Expand All @@ -18,9 +19,20 @@ type GLPicture interface {
Texture() *glhf.Texture
}

// pictureDataGLCache memoizes GLPicture uploads keyed by source *pixel.PictureData.
// Without this, every (sprite, canvas) pair re-uploads and retains a full-size pixel
// copy; on 32-bit WASM (~4GB heap) a 4096x4096 atlas blows the heap after a handful
// of draw targets.
var pictureDataGLCache sync.Map // *pixel.PictureData -> GLPicture

// NewGLPicture creates a new GLPicture with it's own static OpenGL texture. This function always
// allocates a new texture that cannot (shouldn't) be further modified.
func NewGLPicture(p pixel.Picture) GLPicture {
if pd, ok := p.(*pixel.PictureData); ok {
if cached, found := pictureDataGLCache.Load(pd); found {
return cached.(GLPicture)
}
}
bounds := p.Bounds()
bx, by, bw, bh := intBounds(bounds)

Expand Down Expand Up @@ -65,6 +77,9 @@ func NewGLPicture(p pixel.Picture) GLPicture {
tex: tex,
pixels: pixels,
}
if pd, ok := p.(*pixel.PictureData); ok {
pictureDataGLCache.Store(pd, gp)
}
return gp
}

Expand Down
10 changes: 5 additions & 5 deletions backends/opengl/glshader.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ uniform vec4 uBounds;

void main() {
vec2 transPos = (uTransform * vec3(aPosition, 1.0)).xy;
vec2 normPos = (transPos - uBounds.xy) / uBounds.zw * 2 - vec2(1, 1);
vec2 normPos = (transPos - uBounds.xy) / uBounds.zw * 2.0 - vec2(1.0, 1.0);
gl_Position = vec4(normPos, 0.0, 1.0);

vColor = aColor;
Expand All @@ -282,14 +282,14 @@ uniform vec4 uTexBounds;
uniform sampler2D uTexture;

void main() {
if ((vClipRect != vec4(0,0,0,0)) && (gl_FragCoord.x < vClipRect.x || gl_FragCoord.y < vClipRect.y || gl_FragCoord.x > vClipRect.z || gl_FragCoord.y > vClipRect.w))
if ((vClipRect != vec4(0.0, 0.0, 0.0, 0.0)) && (gl_FragCoord.x < vClipRect.x || gl_FragCoord.y < vClipRect.y || gl_FragCoord.x > vClipRect.z || gl_FragCoord.y > vClipRect.w))
discard;

if (vIntensity == 0) {
if (vIntensity == 0.0) {
fragColor = uColorMask * vColor;
} else {
fragColor = vec4(0, 0, 0, 0);
fragColor += (1 - vIntensity) * vColor;
fragColor = vec4(0.0, 0.0, 0.0, 0.0);
fragColor += (1.0 - vIntensity) * vColor;
vec2 t = (vTexCoords - uTexBounds.xy) / uTexBounds.zw;
fragColor += vIntensity * vColor * texture(uTexture, t);
fragColor *= uColorMask;
Expand Down
2 changes: 2 additions & 0 deletions backends/opengl/input.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !js

package opengl

import (
Expand Down
Loading