ForgeLeaf Logo

Consent to our cookie policy 🍪

We use a privacy-friendly, cookie-free analytics tool (Umami). Some cookies are used for functionality only. You may check our Privacy Policy.
A pixel art character in a room full of computers

Drawing your first texture using Forge 🎨

This is my second article in the Forge series. In the previous post, we covered how to set up the starter template and get a simple application running.

Today, we’ll take it a step further and draw our first texture to the screen using the Forge framework.


Setting Up the Project 🏗️

We’ll start by cloning the Forge Starter Template. It provides a clean base for new projects:

git clone https://github.com/ForgeLeaf/ForgeStarterTemplate.git
cd ForgeStarterTemplate

Inside the folder, you’ll see a familiar structure. We’ll create an assets/ directory where we’ll place our image, you may do that using mkdir assets or from within your IDE.

You can download the Logo file here.

Add the downloaded Logo.png file to your newly created asset folder.

That’s all we need. Forge will automatically load this file from disk when we create a Texture object.


Loading and Rendering the Texture 🧱

Let’s update our main.go file. We’ll start by defining our Application struct with three main components:

type Application struct {
    logo     *Graphics.Texture
    batch    *Graphics.Batch
    viewport Viewports.Viewport
}
  • logo is our texture object, it’s what we’ll load the Logo.png file into.
  • batch is responsible for the 2D rendering, it’s what we’ll use to render our logo.
  • viewport manages our camera, it’s what we’ll use to set the batch projection and render to the screen-space.

Preparing the Objects 🧩

We load our objects within the Create() method, which runs once at startup. For our texture, logo, we’ll use the Graphics.NewTexture(filePath) method:

application.logo, _ = Graphics.NewTexture("assets/Logo.png")

The method takes in a filePath as parameter, we’ll pass the relative path to our logo asset here. Notice that it returns two objects: a texture object, and an optional error. We may capture the error for debugging but it’s not necessary in our case, that’s why I renamed the optional parameter to an underscore instead of err.

Before continuing, you’ll have to add the following imports for this to work:

  • import _ image/png –> This is used to enable the texture loader to decode the PNG format.
  • import github.com/ForgeLeaf/Forge/Graphics –> This allows us to use the Graphics package.

Now that we’ve loaded our texture into the GPU (The NewTexture does this for us), we’ll need to create a batch instance to render it. The batch is what allows us to easily render textures, texture regions, shapes, and much more using simple method calls.

You can instantiate a new batch using Graphics.NewBatch(driver), we’ll do that instantly after loading the texture:

application.batch = Graphics.NewBatch(driver)

The driver argument is the same parameter that we get from our Create() method.


Setting up the Viewport 🪟

We’ve already loaded our texture and instantiated the batch for rendering, all we have to do now is to specify where we want to render our texture to. We could do that by creating a so-called Camera, but that’s a bit hard to manage for beginners and not recommended. Our solution is to use a Viewport, there are many kinds of viewports and future tutorials will showcase all the built-in ones.

The viewport manages our camera and fixes the headache that comes with resizing, it also allows us to define our space using our own world-units (for ex. 100 pixels = 1 world-unit). For now, we’ll use a ScreenViewport, which maps 1 world-unit to 1 pixel on the screen.

We’ll create our viewport after the batch within our Create() method aswell:

application.viewport = Viewports.NewScreenViewport(driver.Width, driver.Height)

The NewScreenViewport method needs two parameters to create the object: screenWidth and screenHeight, which are the initial dimensions of our window.

Notice that we used the Viewports package and not the Graphics package here, so we’ll have to import it:

import "github.com/ForgeLeaf/Forge/Graphics/Viewports"

Drawing on the Screen 🖥️

Next, we’ll draw our texture inside the Render() method:

func (application *Application) Render(driver *Forge.Driver, delta float32) {
    gl.ClearColor(0, 0, 0, 1)
    gl.Clear(gl.COLOR_BUFFER_BIT)

    batch := application.batch
    viewport := application.viewport

    viewport.Apply(true)
    batch.SetProjection(viewport.GetCamera().Matrix)

    batch.Begin()
    batch.Draw(application.logo, 0, 0, driver.Width, driver.Height)
    batch.End()
}

Let’s break this down:

  • The first two lines clear the screen with a black background.
  • We apply the viewport and camera projection.
  • batch.Begin() starts the drawing phase.
  • batch.Draw(...) renders our image stretched to the window size.
  • batch.End() finishes and submits the draw calls to the GPU.

Before running the code, you’ll have to import the go-gl package to actually use the gl API (first two lines in the method above), you’ll do that by importing this package:

import "github.com/go-gl/gl/v3.3-core/gl"

You may have to run the following command for your environment to fetch the required libraries:

go mod tidy

It looks stretched, right? We have several ways to fix this, the most simple one is to just resize our window to have the same dimensions as the our image. We’ll do that by modifying the configuration passed to the initial Forge.RunSafe(...) call in our main() method.

Change your main() method to look like this:

func main() {
	config := Forge.DefaultDesktopConfig()
	config.Title = "Drawing your first texture using Forge"
	config.Width = 512
	config.Height = 512
	config.Resizable = false
	if err := Forge.RunSafe(&Application{}, config); err != nil {
		panic(err)
	}
}

To break it down:

  • We first get ourselves the default desktop config
  • We change the initial window title to “Drawing your first texture using Forge”
  • We set the initial window width and height to 512 (pixels)
  • We make the window unresizable
  • We give our config to Forge and let it run our application safely.

Alright, now you’ll get something that at least doesn’t look stretched:


Handling Resize Events 🔃

Our current solution worked, but it’s not really ideal to prevent the window from resizing. If you’d like to keep the window resizable and instead provide your own solution, we’d recommend you use a different viewport or unlink the rendering bounds from the window dimensions.

A viewport has to always be notified about resize events. Luckily, Forge has a resize callback called Resize(), just add this snippet to update your viewports’ internal dimension:

application.viewport.Update(width, height, true)

That’ll fix many rendering issues that you’ll set in your journey.


Cleanup 🧹

The objects we created aren’t managed by the GC and it’s our responsibilty to dispose them. Texture, for example, are saved on the GPU and have to be removed manually.

Forge includes a handy Dispose() method in all objects that have to be manually disposed. These are mainly graphics objects that use the GPU directly, in our case it’s the logo texture and our batch instance (the batch has internal shaders uploaded to the GPU).

Within your dispose callback, which’s called Dispose(), you’ll have to add these two lines:

application.logo.Dispose()
application.batch.Dispose()

That’ll take care of freeing the memory of these objects.


Putting It All Together 🪄

Now, a bit of optimization and a magic touch and your final code should look similar to this:

package main

import (
	"runtime"

	_ "image/png"

	"github.com/ForgeLeaf/Forge"
	"github.com/ForgeLeaf/Forge/Graphics"
	"github.com/ForgeLeaf/Forge/Graphics/Viewports"
	"github.com/go-gl/gl/v3.3-core/gl"
)

type Application struct {
	logo     *Graphics.Texture
	batch    *Graphics.Batch
	viewport Viewports.Viewport
}

func (application *Application) Create(driver *Forge.Driver) {
	application.logo, _ = Graphics.NewTexture("assets/Logo.png")
	application.batch = Graphics.NewBatch(driver)
	application.viewport = Viewports.NewScreenViewport(driver.Width, driver.Height)
}

func (application *Application) Render(driver *Forge.Driver, delta float32) {
	gl.ClearColor(0, 0, 0, 1)
	gl.Clear(gl.COLOR_BUFFER_BIT)

	batch := application.batch
	viewport := application.viewport

	viewport.Apply(true)
	batch.SetProjection(viewport.GetCamera().Matrix)

	batch.Begin()
	batch.Draw(application.logo, 0, 0, driver.Width, driver.Height)
	batch.End()
}

func (application *Application) Resize(driver *Forge.Driver, width, height float32) {
	application.viewport.Update(width, height, true)
}

func (application *Application) Destroy(driver *Forge.Driver) {
	application.logo.Dispose()
	application.batch.Dispose()
}

func init() {
	runtime.LockOSThread()
}

func main() {
	config := Forge.DefaultDesktopConfig()
	config.Title = "Drawing your first texture using Forge"
	config.Width = 512
	config.Height = 512
	config.Resizable = false
	if err := Forge.RunSafe(&Application{}, config); err != nil {
		panic(err)
	}
}

Compare it to your version and tell me if it works! That’s it! You’ve successfully drawn your first texture with Forge. If you’ve faced any issues, please message me using the form below.

You can find the repository for this tutorial here: https://github.com/ForgeLeaf/YourFirstTexture-Forge


Common Issues

If you’re getting a black screen:

  • Check if you imported the image/png package.
  • Check if you’re rendering within batch.Begin() and batch.End().
  • Check if you’re applying the viewport projection to the batch.

If the code isn’t compiling:

  • Check if you’ve misspelled any imports.
  • Run go mod tidy and rebuild.
  • Clear IDE cache.
  • Check for syntax errors.

If your program is crashing before rendering anything:

  • Check whether you’ve imported the correct go-gl version or not (Correct one is go-gl/gl/v3.3-core/gl).

If you’re facing any other issues, contact me below!

Give us some feedback!

Thanks for reading our article. Could you please consider giving us some constructive, anonymous feedback?

Related articles

Working with ECS in LibGDX 🤩

Bismillah, dear readers!I hope you enjoyed the long weekend and managed to recharge. Today we’ll explore building a simple game using simple‑world‑gdx (my lightweight ECS for LibGDX), together with simple‑batch‑gdx…
Read more

Forge Framework

Forge is our free and open source game framework for the GoLang programming language. It is feature-rich and constantly improved. We designed Forge to be performant and simple for beginner, its power lies within its ecosystem and community. Forge is lightweight in nature and can be easily extended to fit most of your needs. Write you game logic in your way and let the framework handle compatibility.
Community

Connect with our community

ForgeLeaf Logo
Join the ForgeLeaf community — follow updates, contribute, and grow together.

Subscribe to our Newsletter

Stay Updated on the Latest Dev Insights. Join our mailing list to receive new articles, tutorials, and project updates.
ForgeLeaf Logo
Made with ❤️ in 🇩🇪
Follow us on social media
Subscribe to our Newsletter
© 2025 ForgeLeaf Studio