Efficient drawing... wat.

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • Narvius
    Knight
    • Dec 2007
    • 589

    Efficient drawing... wat.

    I'm trying to write a roguelike (not based on Angband, per se, but peeking into its code from time to time, both to see how to do certain things and how NOT to do certain things).

    Now, in Angband, when you start smashing random move buttons, everything gets updated immediately - no noticeable movement lag whatsoever.

    My SDL-based mini-prototype, however... it's not anywhere near as fast. The screen isn't even 800x600, and redraws on keypresses (that's not entirely true, but a workable approximation). When I mash buttons, there is some really noticable lag - 20-40ms? Not sure.

    Anyways. Due to this - since I couldn't find 'em in the code, so it's maybe better to ask the people who have a working understanding of it - I've been wondering:

    1) ...what the hell is the underlying graphics-drawing thing? (I'm using the default Win7 binaries)
    2) What triggers screen redraws?
    3) How heavily is the screen-redrawing routine itself optimized?

    Thanks in advance.
    If you can convincingly pretend you're crazy, you probably are.
  • Derakon
    Prophet
    • Dec 2009
    • 9022

    #2
    I'd guess that your system is redrawing the entire screen every frame. Unfortunately, this is slow, especially with simple loops and software rendering (i.e. not relying on hardware acceleration). There are a few optimizations you can do without dipping into OpenGL, though.

    First and foremost, you want to keep track of which tiles have been modified between one draw call and the next. These are your "dirty" tiles. Don't bother drawing any others. So for example, if a monster moves from tile A to tile B, then both tiles are now dirty and must be redrawn. Everything else can just stay put.

    Secondly, map scrolling is fairly common. Since all of the tiles are "modified" by map scrolling, it doesn't work under the above mechanism. But what you can do is draw the current map to the screen, but offset by the scrolling amount. Then fill in dirty tiles as normal. So e.g. if you scroll the screen 1 tile to the right, then all you need to draw is the current map, offset 1 tile, plus a border of tiles (newly revealed by the scrolling), and any dirty tiles.

    Thirdly, if you have overlays that can be shown and hidden, keep those on separate surfaces. For example, keep the map and the inventory displays on separate surfaces; when the inventory is hidden, just draw the map as it was, unmodified.

    The bottom line is that each blit call you make exacts a constant cost in addition to the time it spends doing the actual drawing. Eliminating blit calls will save you a lot of time, therefore.

    The actual drawing code Angband uses is going to be OS-dependent, so look at main-win or main-cocoa or whatever. But I'd wager you can get similar performance by a) doing the above, and b) possibly switching to OpenGL-based rendering. Stick every glyph you want to be able to render onto a texture, and then throw textured quads around. You can send entire arrays of coordinates to OpenGL as a stream of data, and then farm things off to hardware, which is very fast.

    Comment

    • Narvius
      Knight
      • Dec 2007
      • 589

      #3
      Whoah - that was quick, many thanks.

      It seems I'll be spending some of them hours on my drawing code then.
      If you can convincingly pretend you're crazy, you probably are.

      Comment

      • AnonymousHero
        Veteran
        • Jun 2007
        • 1393

        #4
        In short: Use double buffering and always draw into an in-memory bitmap/texture. (Regardless of whether you're using 2D or OpenGL.)

        If you're using X11, avoid X11 calls at all costs -- they synchronous and at least two orders of magnitude slower than in-memory operations. Use Cairo (or something similar) to draw in-memory and then blit the surface to screen in a single X11 operation. All Angband variants that I know of disobey this rule (they issue one X11 operation per "cell" on screen) and fail miserably when scrolling or using "Center screen on player".

        Comment

        • takkaria
          Veteran
          • Apr 2007
          • 1951

          #5
          Originally posted by AnonymousHero
          In short: Use double buffering and always draw into an in-memory bitmap/texture. (Regardless of whether you're using 2D or OpenGL.)

          If you're using X11, avoid X11 calls at all costs -- they synchronous and at least two orders of magnitude slower than in-memory operations. Use Cairo (or something similar) to draw in-memory and then blit the surface to screen in a single X11 operation. All Angband variants that I know of disobey this rule (they issue one X11 operation per "cell" on screen) and fail miserably when scrolling or using "Center screen on player".
          The X11 port is explicitly designed to use no external libraries except Xlib. If you want more, I'd suggest SDL.
          takkaria whispers something about options. -more-

          Comment

          • Derakon
            Prophet
            • Dec 2009
            • 9022

            #6
            For what it's worth, Pyrel uses widget libraries (Qt / wxWidgets) to handle its drawing. In my opinion, they're superior to SDL in that you can have multiple traditional windows, each with a canvas you can draw anything to. You can also use standard OS dialogs easily. SDL can only kind of fake multiple windows and you have to code every UI widget yourself; it's doable, but there's no real reason to force yourself to go that route.

            However, this does mean that you have to use the library's drawing code, or else switch to OpenGL rendering, which is not especially newbie-friendly.

            Comment

            • AnonymousHero
              Veteran
              • Jun 2007
              • 1393

              #7
              Originally posted by takkaria
              The X11 port is explicitly designed to use no external libraries except Xlib. If you want more, I'd suggest SDL.
              AFAIUI, using the XImage type functions in Xlib, it should be possible to draw everything in-memory and then blit the whole final bitmap in one go(*). AFAICT it is the "blit a single character at a time" thing which completely destroys performance on X11/Xlib (because it's synchronous and requires a round-trip to the X server).

              SDL is a non-starter for me (at least) because it only supports a single terminal window. (At least 1.2.x doesn't support more, and it seems 2.x still hasn't made it into very many distributions.) It supports splitting that single window, but that won't let me use mulitple physical screens the way I want.

              (*) I don't know if Xlib supports all drawing operations on XImages -- I'm guessing no -- so I'm not suggesting that this is trivial endeavor if all you can use is Xlib. However, it is the right way to do it if you're writing from scratch. If you use something like Cairo it should be pretty trivial.

              Comment

              • LostTemplar
                Knight
                • Aug 2009
                • 670

                #8
                Use OS default bitmap drawing routines, or just normal ASCII text, again with default drawing, e.g. if you want just text under windows create console application. Use another 'frame buffer' for your 'game engine' to draw to.
                E.g. for a tile based roguelike your game should mogify some array of tile indexes, then call a rendering function, that get images for individual tiles and forms a bitmap, then use standard OS bitmap display function. You can update frames, based on time (for background animation), or after every player turn.

                Partial updates also work, are faster, but somewhat less universal, and may be more buggy.

                Comment

                • AnonymousHero
                  Veteran
                  • Jun 2007
                  • 1393

                  #9
                  Originally posted by LostTemplar
                  Partial updates also work, are faster, but somewhat less universal, and may be more buggy.
                  The sentence is a bit ambiguous, but I take it that you're referring to partial updates from the bitmap, right? (Otherwise round-tripping will probably kill performance because of syscall overhead.)

                  Comment

                  • LostTemplar
                    Knight
                    • Aug 2009
                    • 670

                    #10
                    I thought about creating and then drawing the bitmap, just not full window size, if it is not needed, I would still prefer to limit partial updates to some kind of rectangular area, not like individual tiles or character. Yes I am very concerned about system call overhead cost, it is often very high.

                    Comment

                    • Narvius
                      Knight
                      • Dec 2007
                      • 589

                      #11
                      So I went with OpenGL, and would you believe it - rendering TEXT, yet alone a grid of characters is hard.

                      Since I'm writing in Common Lisp, I ultimately went with:
                      1) Generate picture using Vecto (a library for generating vector graphics, with full ttf-support);
                      2) Store that picture on an SDL surface;
                      3) Convert said surface to an OpenGL Texture.

                      Since each glyph is only generated as texture once, that convoluted routine isn't too time-consuming. :P
                      If you can convincingly pretend you're crazy, you probably are.

                      Comment

                      • Derakon
                        Prophet
                        • Dec 2009
                        • 9022

                        #12
                        Yeah, text is always a massive pain in OpenGL. FTGL usually works nicely if there are bindings available in your language of choice, but I remember really having to work to get PyFTGL to build when I wanted to use it for a work project.

                        Comment

                        Working...
                        😀
                        😂
                        🥰
                        😘
                        🤢
                        😎
                        😞
                        😡
                        👍
                        👎