Failed to create software surface

Started by wizard_wizzle (aka ZeldaHistorian), July 31, 2016, 01:52:18 AM

Previous topic - Next topic
I'm using a modified version of Satoh's code that was posted at http://forum.solarus-games.org/index.php/topic,676.15.html to make the day/night system I had work with the new blend modes introduced in 1.5. It uses the map metatable to force a night overlay to drawn on all maps when it's night time. The problem is that after 10 to 15 minutes of game play at night (an overlay with blend modes being drawn) my game crashes with the fatal error "Failed to create software surface". I'm assuming that my code is just inefficient and the repeated re-drawing is causing memory issues, but I'm not sure how to improve it. Hopefully someone here will have an idea that can help me improve me code! And thanks for looking!

Code ("lua") Select
  function map_metatable:on_draw(dst_surface)
    local game = self:get_game()
    local hour_of_day = (time_counter / 3000)
    -- Put the night (or sunrise or sunset) overlay on any outdoor map if it's night time.
    if (hour_of_day >= 19.6 or hour_of_day <= 7.4) and
    (game:is_in_outside_world() or (self:get_world() == "dungeon_2" and self:get_id() == "20") or
  (self:get_world() == "dungeon_2" and self:get_id() == "21") or (self:get_world() == "dungeon_2" and self:get_id() == "22")) then
      local x,y = game:get_map():get_camera():get_position()
      local w,h = game:get_map():get_camera():get_size()
     
      if draw_counter >= 15 then
        shadow:clear()
        if hour_of_day >= 19.6 and hour_of_day < 20 then
          t[1] = 255; t[2] = 255; t[3] = 255
          -- Dusk
          if t[1] >= 2 then
            t[1] = t[1] - 2 -- Red: Goal is 0 (fade to black, which is the same as fading in for this blend mode)
          else
            game:set_value("time_of_day", "night")
            game:set_value("hour_of_day", 20)
          end
          if t[2] >= 3 then
            t[2] = t[2] - 3 -- Green: Goal is 0
          else
            game:set_value("time_of_day", "night")
            game:set_value("hour_of_day", 20)
          end
          if t[3] >= 4 then
            t[3] = t[3] - 4 -- Blue: Goal is 0
          else
            game:set_value("time_of_day", "night")
            game:set_value("hour_of_day", 20)
          end
          shadow:fill_color(t)
        elseif hour_of_day >= 20 and hour_of_day <= 21 then
          -- Sunset
          if t[1] >= 32 then t[1] = t[1] - 3 end -- Red: Goal is 32 (from 255)
          if t[2] >= 64 then t[2] = t[2] - 2 end -- Green: Goal is 64 (from 255)
          if t[3] >= 128 then t[3] = t[3] - 1 end  -- Blue: Goal is 128 (from 255)
          shadow:fill_color(t)
        elseif hour_of_day >= 6 and hour_of_day <= 7 then
          -- Sunrise
          t[1] = (182*(hour_of_day-6))+18 -- Red: Goal is 182 (from 32)
          t[2] = (62*(hour_of_day-6))+66 -- Green: Goal is 126 (from 64)
          t[3] = (37*(1-(hour_of_day-6)))+87 -- Blue: Goal is 91 (from 128)
          shadow:fill_color(t)
        elseif hour_of_day > 7 and hour_of_day <= 7.4 then
          -- Dawn
          if t[1] <= 253 then
            t[1] = t[1] + 2 -- Red: Goal is 255 (fade to white, which is the same as fading out for this blend mode)
          else
            game:set_value("time_of_day", "day")
            game:set_value("hour_of_day", 7.5)
          end
          if t[2] <= 252 then
            t[2] = t[2] + 3 -- Green: Goal is 255
          else
            game:set_value("time_of_day", "day")
            game:set_value("hour_of_day", 7.5)
          end
          if t[3] <= 251 then
            t[3] = t[3] + 4 -- Blue: Goal is 255
          else
            game:set_value("time_of_day", "day")
            game:set_value("hour_of_day", 7.5)
          end
          shadow:fill_color(t)
        else
          -- Night
          shadow:fill_color({32,64,128,255})
          game:set_value("time_of_day", "night")
        end
       
        lights:clear()
        for e in game:get_map():get_entities("torch_") do
          if e:get_sprite():get_animation() == "lit" and e:get_distance(game:get_hero()) <= 250 then
            local xx,yy = e:get_position()
            local sp = sol.sprite.create("entities/torch_light")
            sp:set_blend_mode("blend")
            sp:draw(lights, xx-32, yy-32)
         end
        end
        for e in game:get_map():get_entities("night_") do
          if e:is_enabled() and e:get_distance(game:get_hero()) <= 250 then
            local xx,yy = e:get_position()
            local sp = sol.sprite.create("entities/torch_light")
            sp:set_blend_mode("blend")
            sp:draw(lights, xx-24, yy-24)
          end
        end
        for e in game:get_map():get_entities("lava_") do
          if e:is_enabled() and e:get_distance(game:get_hero()) <= 250 then
            local xx,yy = e:get_position()
            local sp = sol.sprite.create("entities/torch_light_tile")
            sp:set_blend_mode("blend")
            sp:draw(lights, xx-8, yy-8)
          end
        end
        for e in game:get_map():get_entities("warp_") do
          if e:is_enabled() and e:get_distance(game:get_hero()) <= 200 then
            local xx,yy = e:get_position()
            local sp = sol.sprite.create("entities/torch_light_tile")
            sp:set_blend_mode("blend")
            sp:draw(lights, xx-16, yy-16)
          end
        end
        for e in game:get_map():get_entities("poe") do
          if e:is_enabled() and e:get_distance(game:get_hero()) <= 200 then
            local xx,yy = e:get_position()
            local sp = sol.sprite.create("entities/torch_light")
            sp:set_blend_mode("blend")
            sp:draw(lights, xx-32, yy-32)
          end
        end
        -- Slowly drain magic when using lantern.
        if magic_counter >= 50 then
          game:remove_magic(1)
          magic_counter = 0
        end
        if not game:is_suspended() then magic_counter = magic_counter + 1 end
        draw_counter = 0
      end
      draw_counter = draw_counter + 1

      if game:has_item("lamp") and game:get_magic() > 0 then
        local xx, yy = game:get_map():get_entity("hero"):get_position()
        local sp = sol.sprite.create("entities/torch_light_hero")
        sp:set_blend_mode("blend")
        sp:draw(lights, xx-64, yy-68)
      end

      lights:draw_region(x,y,w,h,shadow,x,y)
      shadow:draw_region(x,y,w,h,dst_surface)
    end
  end

I guess this is still this bug: https://github.com/christopho/solarus/issues/933
This error message probably means a bug of the engine. I am trying to reproduce it by running your project.
Does it also happens if you run without 2D acceleration?

Even if there might be an engine bug, it is wrong to modify the state in on_draw() events (like changing savegame values). The on_draw() calls are not guaranteed, some of them are skipped sometimes if the machine is too slow. A quest should still work the same independently of how often the screen is redrawn.

On the contrary, on_update() is called exactly once per frame.

Additionally, creating surfaces or sprites in on_draw() is also wrong, because for better performance you should create them once rather than at each frame. However, it should still work, it is just not great for performance.


I'm rebuilding the surfaces because the lights may change and the player moves, so the light around him has to move as well. I don't know of another way to do this. I could probably move some of the non-drawing functions to on_update instead, but I don't know how much difference it will make since I think the majority of the overhead is coming from the drawing itself.

Build the surfaces should be okay in on_draw(), but loading sprites can be done from the outside.
Also, update the time/hour of day in the savegame should be done from on_update() or from a timer to be reliable.

I am still trying to reproduce the crash, without success so far. Does it happens with and without 2D acceleration? Do you see the memory usage grow before the crash? (i.e., is it an out-of-memory error?)

July 31, 2016, 07:51:10 PM #5 Last Edit: July 31, 2016, 07:55:18 PM by MetalZelda
This has something to do with clear() and fill_color, because it is redrawn at each frame in on_draw(), I faced it back when I was doing my day/night cycle.
The solution might be, create a separate script (menu) where the lighting effect will be entirely, and start it from there

Edit : I'm gonna let the game run and see if it does it as well, never tested my project > 20 minutes

Thanks for the replies Christopho and MetalZelda!

I did refactor my code a bit based on Christopho's advice, and so far I've been able to prevent the crash. I do plan to just let my game run for an hour or so and see if I get it then, but by playtesting so far has been fine.

MetalZelda, I think it's a good idea to let your game run for a while to ensure you don't get the crash. If you do, move some stuff out of on_draw like Christopho suggests.

July 31, 2016, 09:01:21 PM #7 Last Edit: July 31, 2016, 09:04:48 PM by MetalZelda
Quote from: wrightmat on July 31, 2016, 08:16:29 PM
Thanks for the replies Christopho and MetalZelda!

I did refactor my code a bit based on Christopho's advice, and so far I've been able to prevent the crash. I do plan to just let my game run for an hour or so and see if I get it then, but by playtesting so far has been fine.

MetalZelda, I think it's a good idea to let your game run for a while to ensure you don't get the crash. If you do, move some stuff out of on_draw like Christopho suggests.

Oh, this is already done, most of my scripts now runs from a menu (shop management, day/night tone, hyrule field dynamic audio, items manager, title screen manager) so map:on_draw() is always clear
I'm gonna run the project for a straight 30 minutes and see if I got a crash

Okay so I tested your project Wrightmatt and I still got "failed to create software surface" in some place, and this error only occur in places where the light effect is used (I got it in Mountaintop dungeon) while fighting the miniboss, and there are huge lags when the light effect reload (every seconds)

Hmmm... were you running the most recent Github version?

Oh! The mountaintop dungeon and the other inside areas that use lights I think I still need to optimize. I'll look at that tomorrow, lol

Quote from: wrightmat on August 03, 2016, 12:04:41 AM
Hmmm... were you running the most recent Github version?

Oh! The mountaintop dungeon and the other inside areas that use lights I think I still need to optimize. I'll look at that tomorrow, lol

Don't think so, I should look at the most recent, but it is clear that this error is caused by inside light effect, the fastest crash I got was in interloper sanctum, basement

I am now almost positive that the crash is related to clear(), as MetalZelda had mentioned earlier. If I change the clear line to fill_color (which appears to have the same effect anyway, at least in my case) I can run the game for hours without a crash.

Closing the discussion here, let's continue in the github issue only: https://github.com/christopho/solarus/issues/933