Page 3 of 4
Re: Any way to get canvases to be more efficient?
Posted: Sat Feb 22, 2020 6:07 pm
by Sky_Render
I'd have to make some fixes to my code right now to get it working again; the SpriteBatch experiment really messed it up (good thing I didn't delete my original render code!). I have been working on that while we've been talking, though, and once I've got it fixed I will be posting what I've got so far.
For a decent example of similar visual effect, just about any modern 2D top-down-Zelda-style game with lots of layering would suffice. Graveyard Keeper, Stardew Valley, and Undermine all come to mind as decent examples of games of the style in question.
Re: Any way to get canvases to be more efficient?
Posted: Sat Feb 22, 2020 6:12 pm
by raidho36
Sky_Render wrote: ↑Sat Feb 22, 2020 6:07 pm
For a decent example of similar visual effect, just about any modern 2D top-down-Zelda-style game with lots of layering would suffice. Graveyard Keeper, Stardew Valley, and Undermine all come to mind as decent examples of games of the style in question.
There's absolutely nothing in there that warrants using more than one layer.
Re: Any way to get canvases to be more efficient?
Posted: Sat Feb 22, 2020 6:17 pm
by Sky_Render
raidho36 wrote: ↑Sat Feb 22, 2020 6:12 pm
Sky_Render wrote: ↑Sat Feb 22, 2020 6:07 pm
For a decent example of similar visual effect, just about any modern 2D top-down-Zelda-style game with lots of layering would suffice. Graveyard Keeper, Stardew Valley, and Undermine all come to mind as decent examples of games of the style in question.
There's absolutely nothing in there that warrants using more than one layer.
Oh really? So how exactly would one recognize foreground? Or background that doesn't have shadows rendered over it? And I guess lighting effects are also not a thing? Your statement does not make sense to me.
Re: Any way to get canvases to be more efficient?
Posted: Sat Feb 22, 2020 6:42 pm
by Sky_Render
As promised, now that I've managed to resurrect my working code, here's what it's doing. This is called once when the game loads:
Code: Select all
function render_layers()
-- Renders all layers of the selected map
local i = 1
-- Draw background
while i <= map_lastshadow[current_map] do
if i ~= map_collisionlayer[current_map] then
draw_layer(current_map, i)
draw_layer(current_map, i, 1)
if current_season == 0 then draw_layer(current_map, i, 2) end
end
i = i + 1
end
-- Draw above-shadow-below-entity background layers
i = map_lastshadow[current_map] + 1
while i < map_foreground[current_map] do
if i ~= map_collisionlayer[current_map] then
draw_layer(current_map, i)
draw_layer(current_map, i, 1)
if current_season == 0 then draw_layer(current_map, i, 2) end
end
i = i + 1
end
-- Draw foreground
i = map_foreground[current_map]
while i <= map_layer_count[current_map] do
if i ~= map_collisionlayer[current_map] then
draw_layer(current_map, i)
draw_layer(current_map, i, 1)
if current_season == 0 then draw_layer(current_map, i, 2) end
end
i = i + 1
end
end
And draw_layer looks like this:
Code: Select all
function draw_layer(index, layer, layertype)
-- Draws a layer from the specified map
local hold_r = hold_r
local hold_g = hold_g
local hold_b = hold_b
local tile_xsize = tile_xsize[index]
local tile_ysize = tile_ysize[index]
local map_xsize = map_xsize[index]
local map_ysize = map_ysize[index]
local i = 1
local j = 1
local l = 1
local rendertile
local selecttileset = 1
local pickset = "tileset_tiles"
if setting_map == false then
if layertype == 1 then g.setCanvas(snow_layer[layer])
elseif layertype == 2 then g.setCanvas(transition_layer[layer])
else g.setCanvas(map_layer[layer]) end
g.clear()
end
while j <= map_ysize do
while l <= map_xsize do
pickset = "tileset_tiles"
if tile[index][layer] then if tile[index][layer][l] then if tile[index][layer][l][j] then
if tile[index][layer][l][j] ~= 0 then
rendertile = tile[index][layer][l][j]
-- Check to see if the tile's value exceeds the currently-checked tileset offset
selecttileset = 1
while map_tileset[index][i] do
if tile[index][layer][l][j] >= map_tileset_offset[index][i] then
-- Check if an offset exists higher than this
if not map_tileset_offset[index][i + 1] then
-- No higher offset that meet this criteria, so use this one
rendertile = tile[index][layer][l][j] - map_tileset_offset[index][i] + 1
selecttileset = i
elseif i > 1 then -- This one is too big, use the previous one (but only if not first index!)
rendertile = tile[index][layer][l][j] - map_tileset_offset[index][i] + 1
selecttileset = i
end
end
i = i + 1
end
i = 1
selecttileset = map_tileset[index][selecttileset]
if tileset_variants[selecttileset] then
if not layertype then
if current_season == 2 or (current_season == 0 and current_subseason == 3) then
pickset = "tileset_summertiles"
elseif current_season == 3 or (current_season == 0 and current_subseason == 4) then
pickset = "tileset_autumntiles"
elseif current_season == 4 or (current_season == 0 and current_subseason == 1) then
pickset = "tileset_wintertiles"
else
pickset = "tileset_tiles"
end
else
if current_subseason == 1 then pickset = "tileset_tiles"
elseif current_subseason == 2 then pickset = "tileset_summertiles"
elseif current_subseason == 3 then pickset = "tileset_autumntiles"
elseif current_subseason == 4 then pickset = "tileset_wintertiles" end
end
end
if layertype == 1 then pickset = "tileset_snowtiles" end
-- Store diggable and water info; this is the only place it made sense to set this...
if tileset_diggable[selecttileset][rendertile] == true and not tile_diggable[index][l][j] then
tile_diggable[index][l][j] = true
tile_digimage[index][l][j] = ""
tile_waterimage[index][l][j] = ""
tile_state[index][l][j] = "normal"
tile_tilled[index][l][j] = false
tile_ploughed[index][l][j] = false
tile_trenched[index][l][j] = false
tile_trenched_filled[index][l][j] = false
end
if tileset_water[selecttileset][rendertile] == true and not tile_water[index][l][j] then
tile_water[index][l][j] = true
end
if tileset_animation[selecttileset][rendertile - 1] then rendertile = tileset_animation_frame[selecttileset][rendertile - 1][tileset_animation[selecttileset][rendertile - 1]] + 1 end
-- Render the tile; if this is a blockmap render, we do it a little differently!
if map_layer_opacity[index][layer] then g.setColor(hold_r, hold_g, hold_b, map_layer_opacity[index][layer]) end
if setting_map then gdraw(tileset_tiles[selecttileset][rendertile], ((l * tile_xsize) - tile_xsize), ((j * tile_ysize) - tile_ysize))
else if _G[pickset][selecttileset][rendertile] then gdraw(_G[pickset][selecttileset][rendertile], ((l * tile_xsize) - tile_xsize), ((j * tile_ysize) - tile_ysize)) end
end end end end end
g.setColor(hold_r, hold_g, hold_b, base_a)
l = l + 1
end
l = 1
j = j + 1
end
if setting_map == false then g.setCanvas() end
end
In love.draw it's called thusly in 3 different variants depending on background, non-shadow background, and foreground:
Code: Select all
while i <= map_lastshadow[current_map] do
if i ~= map_collisionlayer[current_map] then
gdraw(map_layer[i], screen_x, screen_y, base_rot, scale_x, scale_y)
g.setColor(base_r, base_g, base_b, snow_level / 100)
gdraw(snow_layer[i], screen_x, screen_y, base_rot, scale_x, scale_y)
g.setColor(base_r, base_g, base_b, base_a)
end
i = i + 1
end
Re: Any way to get canvases to be more efficient?
Posted: Sat Feb 22, 2020 7:16 pm
by raidho36
This code looks like you took Durgasoft courses a little too unironically.
You can start by not using globals and not using nondescript numerical indices. Not grinding through a bunch of static data for every single tile would also help. Finally you can organize render order of your tiles by GPU draw groups and not by coordinate axis. That's as far as I'd put it because the entire thing needs a complete rewrite, but given that you made this in the first place that might actually be a counter-productive advice. Without changing the way you handle effects, it should look something like this:
Code: Select all
for x = x_visible_min, x_visible_max do
for y = y_visible_min, y_visible_max do
table.insert ( rendertiles, tiles[layer][x][y] )
end
end
table.sort ( rendertiles, function ( a, b ) return a.priority < b.priority end )
for i = 1, #rendertiles do
spritebatch:add ( rendertiles[i].sprite, rendertiles[i].x, rendertiles[i].y )
end
love.graphics.draw ( spritebatch, camera_x, camera_y )
The tile sprite isn't needed to be dynamically selected in real time for every single tile, when a trigger condition occurs that requires a tile to change its sprite, do so in update routine.
Re: Any way to get canvases to be more efficient?
Posted: Sat Feb 22, 2020 7:30 pm
by Sky_Render
raidho36 wrote: ↑Sat Feb 22, 2020 7:16 pm
This code looks like you took Durgasoft courses a little too unironically.
You can start by not using globals and not using nondescript numerical indices. Not grinding through a bunch of static data for every single tile would also help. Finally you can organize render order of your tiles by GPU draw groups and not by coordinate axis. That's as far as I'd put it because the entire thing needs a complete rewrite, but given that you made this in the first place that might actually be a counter-productive advice. Without changing the way you handle effects, it should look something like this:
Code: Select all
for x = x_visible_min, x_visible_max do
for y = y_visible_min, y_visible_max do
table.insert ( rendertiles, tiles[layer][x][y] )
end
end
table.sort ( rendertiles, function ( a, b ) return a.priority < b.priority end )
for i = 1, #rendertiles do
spritebatch:add ( rendertiles[i].sprite, rendertiles[i].x, rendertiles[i].y )
end
love.graphics.draw ( spritebatch, camera_x, camera_y )
The tile sprite isn't needed to be dynamically selected in real time for every single tile, when a trigger condition occurs that requires a tile to change its sprite, do so in update routine.
I don't really get most of what you just said, I'll be perfectly honest with you. I've only just realized that Canvases just store the instructions given to them and parse them when called via the draw command, which explains a lot about the issues I'm running into. Now that I get that, however, I think I have some ideas of how to improve things.
Re: Any way to get canvases to be more efficient?
Posted: Sat Feb 22, 2020 7:45 pm
by raidho36
Sky_Render wrote: ↑Sat Feb 22, 2020 7:30 pmI've only just realized that Canvases just store the instructions given to them and parse them when called via the draw command
Uh, no? That would be spritebatch (and technically it just combines sprite geometry into a single megasprite). Canvas is the same as the screen, except not rigidly tied to the actual screen.
There are three ways you optimize things:
1) don't do it
2) do it less often
3) do it using less resources
I'm pretty sure at no point you need to check if tiles are within offsets, because you can simply generate tiles that would never violate this requirement, so
don't do it. I'm pretty sure your seasons don't change every frame for every tile individually and you can just loop over the tiles and enable different graphics once seasons actually change, so
do it less often. Finally you just brute force render all your tiles left to right ,top to bottom. Autobatching will give you some mileage but randomly dropped tiles from different tilesets gonna break it. One way to go about it is not to have different tilesets, to have one megaset that covers for the entire level (possibly the entire game). Another way is to sort the tiles by the tileset such that it doesn't need to switch back and forth for no good reason. It's not difficult to reduce the amount of draw calls,
do it using less resources.
Re: Any way to get canvases to be more efficient?
Posted: Mon Feb 24, 2020 12:07 pm
by zorg
Sky_Render wrote: ↑Sat Feb 22, 2020 6:17 pm
raidho36 wrote: ↑Sat Feb 22, 2020 6:12 pm
Sky_Render wrote: ↑Sat Feb 22, 2020 6:07 pm
For a decent example of similar visual effect, just about any modern 2D top-down-Zelda-style game with lots of layering would suffice. Graveyard Keeper, Stardew Valley, and Undermine all come to mind as decent examples of games of the style in question.
There's absolutely nothing in there that warrants using more than one layer.
Oh really? So how exactly would one recognize foreground? Or background that doesn't have shadows rendered over it? And I guess lighting effects are also not a thing? Your statement does not make sense to me.
Stencil masks to separate foreground and background, and shaders for shadows and lighting effects utilizing the stencil masks?
By my count, that's still one layer, two at most if you really want entities to be on a separate layer, but then the shader stuff might get a bit more complicated... oh, and maybe add another layer for the GUI.
Re: Any way to get canvases to be more efficient?
Posted: Mon Feb 24, 2020 12:34 pm
by raidho36
GUI just goes on top of everything so it doesn't needs to be a layer. Well actually since everything is rendered in specific order and layers' only purpose is to impose a specific render order - which already exists - there is never any need for layers. I decided not to answer any of that because it's a backwards question. It's like saying that santa claus has to exist because otherwise how are you supposed to get christmas presents.
Re: Any way to get canvases to be more efficient?
Posted: Mon Feb 24, 2020 6:27 pm
by Sky_Render
I've been doing some testing on various rendering in a dedicated program, and have found the following thus far:
- Rendering to a SpriteBatch is limited to about 16 layers for efficiency, and holds many other limitations as well.
- Rendering to an ImageData is limited to about 19 layers for efficiency, and will clip anything pasted outside of the size limitations.
- Rendering to a Canvas is limited to about 19 layers for efficiency, and will not clip edges.
I can't get ArrayImages to test correctly, however; it declares anything I send it besides a filename to be nil, even if it is ImageData, but I suspect it would be no more efficient than ImageData and Canvas are. I cannot seem to find a method that exceeds 19 layers, however. From what I've read on the Wiki, Shaders could potentially help with this, but I'm finding the documentation on them to be hard to comprehend.
EDIT: Adding the basic shader listed on the newShader page upped efficiency to 19, 21, and 21 respectively. Not exactly impressive gains, I feel like I'm still missing something here.