Loading Screen Improvements/Fix Crashes #41

Closed
opened 2021-07-19 20:24:42 +00:00 by kirsle · 0 comments

With the recently added loading screen, gameplay becomes unstable and can crash when transitioning between Edit and Play modes:

  • The loadscreen itself sometimes rendered graphical glitches into its wallpaper.
  • Graphical wallpaper glitches sometimes manifested in the level background too once loaded.
  • Occasionally the game crashes because SDL2 is trying to load a Texture on a non-main thread (wallpapers and sprite icon images on the Editor Mode toolbar).

The root cause is that the setupAsync() functions on the Play and Edit Mode runs on a background goroutine to load the level and pre-render its chunks while not blocking the main thread; without the goroutine, the main thread hangs until loading is 100% completed and the load screen UX never even got a chance to draw!

Similar crashes used to affect the Level Chunker when it pre-computed its chunks: on the goroutine this would happen off the main thread and crash. The Chunker updated to separate the "bitmap generation" phase from the "SDL2 texture loader" phase -- the latter only happens at runtime when the chunk is being made visible on the screen (main thread) and at that point it just loads the pre-made bitmap image into the Texture.

Similar treatment can be applied to Wallpapers and Sprite Images: there must be a separation between preparing a bitmap image in memory and postpone texture loading until needed, on the main thread.

Wallpaper

The Wallpaper struct already has these properties:

type Wallpaper struct {
	Image  *image.RGBA

	// The four parsed images.
	corner *image.RGBA // Top Left corner
	top    *image.RGBA // Top repeating
	left   *image.RGBA // Left repeating
	repeat *image.RGBA // Main repeating

	// Cached textures.
	tex struct {
		corner render.Texturer
		top    render.Texturer
		left   render.Texturer
		repeat render.Texturer
	}
}

The series of events starting in setupAsync seems to be:

  • Canvas.LoadLevel(Engine, Level)
    • Wallpaper.FromFile(Engine, filename, embeddable): loads the image into a Go image.RGBA object.
      • Wallpaper.cache(Engine) pre-slices the Image into the four parsed images.
      • Note: Engine might not need to be passed in at this stage at all.
    • Canvas.Wallpaper.Load() fetches the SDL2 textures from the Wallpaper package to make sure they're loaded. This specifically is where the game can crash.

Canvas.PresentWallpaper() is where the wallpaper textures actually get blotted to the screen, on the main thread, and ideally is where the textures should be lazy loaded from pre-cached bitmaps at.

Some things to change:

  • Canvas.LoadLevel(), Wallpaper.FromFile() and Wallpaper.cache() can probably drop the render.Engine param altogether as they don't require or use the Engine at all.
  • Canvas.Wallpaper.Load() should pull the bitmap images from the wallpaper instead of the textures, and leave the textures null.
  • Canvas.PresentWallpaper() can lazy initialize the textures the first time, when null, from the cached bitmap images.

Sprite Images

The editor scene uses sprite images for the toolbar icons.

In EditorUI.SetupToolbar() it calls sprites.LoadImage() to get a *UI.Image to use as the child widget of a ui.RadioButton.

pkg/sprites/sprites.go#LoadImage(Engine, filename) decodes the PNG image and then attempts to Engine.StoreTexture() immediately, which will fail on non-main thread, before returning ui.ImageFromTexture() to get the ui.Image widget.

The fix should probably live closer to ui.Image itself:

  • ui.Image should be able to cache a Go image.Image value separately from the texture.
  • The texture should be lazy initialized from the image.Image the first time it is rendered on screen (in the main thread).

Changes in the game's code would be:

  • sprites.go#LoadImage() should initialize the ui.Image from bitmap instead of texture.
With the recently added loading screen, gameplay becomes unstable and can crash when transitioning between Edit and Play modes: * The loadscreen itself sometimes rendered graphical glitches into its wallpaper. * Graphical wallpaper glitches sometimes manifested in the level background too once loaded. * Occasionally the game crashes because SDL2 is trying to load a Texture on a non-main thread (wallpapers and sprite icon images on the Editor Mode toolbar). The root cause is that the setupAsync() functions on the Play and Edit Mode runs on a background goroutine to load the level and pre-render its chunks while not blocking the main thread; without the goroutine, the main thread hangs until loading is 100% completed and the load screen UX never even got a chance to draw! Similar crashes used to affect the Level Chunker when it pre-computed its chunks: on the goroutine this would happen off the main thread and crash. The Chunker updated to separate the "bitmap generation" phase from the "SDL2 texture loader" phase -- the latter only happens at runtime _when_ the chunk is being made visible on the screen (main thread) and at that point it just loads the pre-made bitmap image into the Texture. Similar treatment can be applied to Wallpapers and Sprite Images: there must be a separation between preparing a bitmap image in memory and postpone texture loading until needed, on the main thread. ### Wallpaper The Wallpaper struct already has these properties: ```go type Wallpaper struct { Image *image.RGBA // The four parsed images. corner *image.RGBA // Top Left corner top *image.RGBA // Top repeating left *image.RGBA // Left repeating repeat *image.RGBA // Main repeating // Cached textures. tex struct { corner render.Texturer top render.Texturer left render.Texturer repeat render.Texturer } } ``` The series of events starting in setupAsync seems to be: * Canvas.LoadLevel(Engine, Level) * Wallpaper.FromFile(Engine, filename, embeddable): loads the image into a Go image.RGBA object. * Wallpaper.cache(Engine) pre-slices the Image into the four parsed images. * **Note:** Engine might not need to be passed in at this stage at all. * Canvas.Wallpaper.Load() fetches the SDL2 textures from the Wallpaper package to make sure they're loaded. This specifically is where the game can crash. Canvas.PresentWallpaper() is where the wallpaper textures actually get blotted to the screen, on the main thread, and ideally is where the textures should be lazy loaded from pre-cached bitmaps at. Some things to change: * Canvas.LoadLevel(), Wallpaper.FromFile() and Wallpaper.cache() can probably drop the render.Engine param altogether as they don't require or use the Engine at all. * Canvas.Wallpaper.Load() should pull the bitmap images from the wallpaper instead of the textures, and leave the textures null. * Canvas.PresentWallpaper() can lazy initialize the textures the first time, when null, from the cached bitmap images. ### Sprite Images The editor scene uses sprite images for the toolbar icons. In EditorUI.SetupToolbar() it calls sprites.LoadImage() to get a \*UI.Image to use as the child widget of a ui.RadioButton. pkg/sprites/sprites.go#LoadImage(Engine, filename) decodes the PNG image and then attempts to Engine.StoreTexture() immediately, which will fail on non-main thread, before returning ui.ImageFromTexture() to get the ui.Image widget. The fix should probably live closer to ui.Image itself: * ui.Image should be able to cache a Go image.Image value separately from the texture. * The texture should be lazy initialized _from_ the image.Image the first time it is rendered on screen (in the main thread). Changes in the game's code would be: * sprites.go#LoadImage() should initialize the ui.Image from bitmap instead of texture.
kirsle added the
bug
label 2021-07-19 20:24:48 +00:00
Sign in to join this conversation.
No Milestone
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: SketchyMaze/doodle#41
There is no content yet.