Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding a texture atlas extension #103

Merged
merged 1 commit into from
Aug 17, 2024
Merged
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
13 changes: 7 additions & 6 deletions ext/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ to pushing Pixel to the next level.

## Extension List

* [atlas](atlas/README.md) - Texture atlasing for more efficient rendering.
* [gameloop](gameloop/README.md) - An extension that allows you to run a game loop in Pixel.
* [imdraw](imdraw/README.md) - An extension that allows you to draw primitives in Pixel.
* [text](text/README.md) - An extension that allows you to draw text in Pixel.


## Creating a Plugin
## Creating an Extension

Plugins are just a collection of files that are placed in the `plugins` directory. The directory name
is the name of the plugin. The plugin directory must contain a `README.md` file that describes the plugin.
Make sure to provide some example code in the **README** so that users are aware of how to use the plugin.
Extensions are just a collection of files that are placed in the `ext` directory. The directory name
is the name of the extension. The extension directory must contain a `README.md` file that describes the extension.
Make sure to provide some example code in the **README** so that users are aware of how to use the extension.

You are encouraged to create an example of your plugin in the [pixel-examples](https://github.com/gopxl/pixel-examples). This will allow users
to see your plugin in action and how to use it.
You are encouraged to create an example of your extension in the [pixel-examples](https://github.com/gopxl/pixel-examples). This will allow users
to see your extension in action and how to use it.
278 changes: 278 additions & 0 deletions ext/atlas/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
# The Atlas Extension

This extension provides an implementation of a texture atlas. A texture atlas groups individual textures together into a single texture that is sent to the GPU. This lessens the time spent switching GPU textures which can be expensive.

## Usage

You create an atlas by just declaring a variable:

```go
import "github.com/gopxl/pixel/v2/ext/atlas"

var textures atlas.Atlas
```

No initialization needed! You're ready to start adding textures into the atlas.

### Adding Textures

There are a few supported ways to add textures to the atlas:
1. Image Data
2. Loading from a file
3. Loading from an embedded file

Each of these methods returns an `atlas.TextureId`, you'll need to keep track of this value as it's how you draw that individual texture.
The `atlas` package doesn't not enforce how you store them. So you could store them statically in variables:

```go
var (
textures atlas.Atlas
texture1 = textures.AddFile("1.png")
texture2 = textures.AddFile("2.png")
)
```

Or you could dump them into a map with names to lookup:

```go
func run() {
var textures atlas.Atlas

textureMap := make(map[string]atlas.TextureId)

textureMap["1"] = textures.AddFile("1.png")
textureMap["2"] = textures.AddFile("2.png")
}
```

Or whatever other way you can come up with!

##### Adding Image Data

If you've already loaded an image and want to copy it into the atlas you can use this method.

```go
func run() {
var textures atlas.Atlas

f, err := os.Open("1.png")
defer f.Close()

i, err := png.Decode(f)

texture1 := textures.AddImage(i)
}
```

##### Adding an Image File

The atlas also has convience methods to load a file directly from the path.

```go
func run() {
var textures atlas.Atlas

texture1 := textures.AddFile("1.png")
}
```

##### Adding an Embedded Image

The atlas also supports Go's embedded file system.

```go
// go:embed 1.png
var embedded embed.FS

func run() {
var textures atlas.Atlas

texture1 := textures.AddEmbed(embedded, "1.png")
}
```

#### Sliced Textures

Sometimes, you already have a texture that contains multiple sprites of equal size in it; atlas can load these directly, cutting the single texture into multiple textures to easily use.

Instead of an `atlas.TextureId`, creating a sliced texture returns an `atlas.SliceId`. This allows you to access the frames of the sliced texture; we'll show this in more detail later.

Each of the `atlas.Slice` methods take an additional `pixel.Vec`. This is the sub-image size and it tells the atlas how to slice up the texture.

Once you have a `atlas.SliceId`, you can use that to get all of the textures that were added to the atlas

```go
var textures atlas.Atlas

sliced1 := textures.SliceFile("sheet.png", pixel.V(8, 8))
walk0 := sliced1.Frame(0)
walk1 := sliced1.Frame(1)
```

**Note:** If you attempt to index a non-existant frame, a panic will be raised.

##### Slicing Image Data

```go
func run() {
var textures atlas.Atlas

f, err := os.Open("sheet1.png")
defer f.Close()

i, err := png.Decode(f)

sliced1 := textures.SliceImage(f, pixel.V(8, 8))
}
```

##### Slicing Image File

```go
func run() {
var textures atlas.Atlas

sliced1 := textures.SliceFile("sheet1.png", pixel.V(8, 8))
}
```

##### Slicing an Embedded Image

```go
// go:embed sheet1.png
var embedded embed.FS

func run() {
var textures atlas.Atlas

sheet1 := textures.SliceEmbed(embedded, "sheet1.png", pixel.V(8, 8))
}
```

### Packing the Atlas

Once you've added all of the textures to the atlas you wish, it needs to be packed.

This sorts the added textures by size to minimize the atlas texture. It then creates as many `pixel.PictureData` textures as it needs to store everything, then copies all of the added images to those textures.

```go
var textures atlas.Atlas

// ... use the Add* and/or Slice* methods to add textures

textures.Pack()
```

### Drawing Atlas Textures

#### Drawing TextureId

When you have an `atlas.TextureId`, you can draw it to a `pixel.Target` normally.

```go
var textures atlas.Atlas

sprite1 := textures.AddFile("1.png")

textures.Pack()

sprite1.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
```

As you can see, it draws just like the `pixel.Sprite` does, but much more efficiently because this limits the amount of texture changing on the GPU.

#### Drawing SliceId

Drawing an `atlas.SliceId` is slightly different because you need to specify which frame of the sliced texture to draw.

```go
var textures atlas.Atlas

sliced1 := textures.SliceFile("1.png", pixel.V(8, 8))

textures.Pack()

sliced1.Draw(win, pixel.IM.Moved(win.Bounds().Center()), 1)
```

This will draw the second (0-indexed) image in the sliced texture. The index is calculated left to right, top to bottom.

--
OR
--

You could expand out the `atlas.SliceId` into the `atlas.TextureId` that make it up.

```go
var textures atlas.Atlas

sliced1 := textures.SliceFile("sheet.png")
walk0 := sliced1.Frame(0)
walk1 := sliced1.Frame(1)

walk0.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
```

### Groups

Groups are a construct that allow logical grouping of textures for a couple of reasons:

1. You want to have some static textures that are always in the atlas.
2. You want to be able to remove level-specific textures without having to re-add the other textures.
3. You want to use another library that wants to adds its own textures to the atlas without having to worry about its textures being removed out from under it.

You can create a group:

```go
var textures atlas.Atlas

group1 := textures.MakeGroup()
```

Or you could use the default group that comes with the atlas (if you've been following along at home, you've been unknowingly been using this: `atlas.Atlas.Add*` and `atlas.Atlas.Slice*` use the default group).

```go
var textures atlas.Atlas

group1 := textures.DefaultGroup()
```

Groups share the same `Add*` and `Slice*` methods as are on the `atlas.Atlas`.

### Clearing Textures

You can remove all of the textures in an atlas with:

```go
var textures atlas.Atlas

// Add some textures to the atlas

// ...

// Actually, we don't want them anymore
textures.Clear()
```

**Note:** You don't need to call `atlas.Atlas.Pack()` after clearing textures, `atlas.Atlas.Clear()` does this automatically.

#### Clearing Groups

The main feature of groups is being able to remove them from the atlas.

```go
var textures atlas.Atlas

keepThisAroundGroup := textures.MakeGroup()
imGoingToDeleteThisSoon := textures.MakeGroup()

// Add some textures to the groups

// ...

// Actually, we don't want them anymore
textures.Clear(imGoingToDeleteThisSoon)

// Atlas still has all textures added to `keepThisAroundGroup`
```

Loading
Loading