0

This is a NES emulator project I am working on. Every second I need to draw the screen around 60 times to get accurate emulation, which means, every second I have to draw at least 256 * 240 * 60 pixels. For drawing, I am using canvas. On which every frame, I am going through the data of 256 * 240 pixels and drawing them on screen. Unsurprisingly, it was terrible. My emulator draws less than a frame per second

public class GameCanvas extends View{
public void drawSprite(int x, int y, Olc2C02A.Sprite sprite, Canvas canvas){
        for (int ir = 0; ir < sprite.height; ir++){
            for (int ic = 0; ic < sprite.width; ic++){
                Olc2C02A.Pixel p = sprite.getPixel(ic, ir);
                if (p.a != 0){
                    int pr = p.r, pg = p.g, pb = p.b, pa = p.a;
                    painter.setColor(Color.argb(pa, pr, pg, pb));
                    canvas.drawPoint((x + ic), (y + ir), painter);
                }
            }
        }
    }

@Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.parseColor("#000000"));
        drawSprite(0, 0, this.gameScreen, canvas);
    }

}

The above code is a slightly simplified version of my code. In this code I am only drawing pixel by pixel disregarding the actual size of each pixels that would be needed if I considered resizing the frame according to the size of the screen I am running this app on. The sprite is a 256x240 sized array of Pixel objects. Which are custom classes I made to store the color value of each pixel. Therefore, unfortunately, just slight performance boost isn't enough for the emulator. I need to draw at least 30 frames per second if I want my emulator to be functional. What other alternatives can I use for this task that can fit in my android studio project.

  • Not directly an answer but are you sure the Emulator updates all pixels at that rate or does it use some trickery to only alter lines that need to be altered? – Alex Vermeulen Mar 14 '23 at 11:56
  • No such thing I know of. I made a desktop prototype for this project before I started working on the android version. That one follows the same method for drawing frames, it works all right. This project is just an android adaptation of that project @AlexVermeulen – ashrafsusts19 Mar 14 '23 at 12:00
  • just browsed a few other topics like this: https://stackoverflow.com/questions/53157053/low-frame-rate-when-drawing-full-screen-drawable-on-canvas might help you out? otherwise https://stackoverflow.com/questions/46531073/how-to-create-bitmap-form-drawable-object/46531354#46531354 might also help out – Alex Vermeulen Mar 14 '23 at 12:01
  • @AlexVermeulen I have given it a try. Using bitmaps has helped a bit, but not enough. However, I still updated the bitmap values manually through this loop. Maybe if I directly implement it in my NES ppu, the performance will speed up a bit. I will have to try it out to tell if that will work or not – ashrafsusts19 Mar 14 '23 at 12:29
  • @AlexVermeulen Nah, I don't think it will work. Just to test it out if removing the loop helps, I removed the loop and created two bitmaps of 256x240 size, one white, another black. I altered between drawing one bitmap and another. It still draws only about 2-3 frames per second. Like I suspected, I will need something better – ashrafsusts19 Mar 14 '23 at 12:42

1 Answers1

0

Nobody draws pixels. All the games use OpenGL.

Even the GUI of an Android app (buttons/edits/pictures/...) is drawn using OpenGL.

When you draw pixel by pixel you are using the CPU to render the graphics. You are not using the GPU (graphics card) at all. So this will never work.

You have to use GLSurfaceView. You have to use OpenGL functions defined in GLES20, GLES30, ...

See Android OpenGL guide.

The NES graphics is based on quadratic stripes (textures). These stripes are loaded into the NES graphics card and can then be resized, rotated and transformed by the CPU inside the GPU. So the CPU can send a command to the GPU: "Please rotate stripe by 90 degrees" (for example). So the CPU never draws pixels but instead sends commands to the GPU.

On Android you send such commands to the GPU using OpenGL. This makes it fast because the graphics calculation is out sourced to the GPU.

Also note that DirectX is the Microsoft equivalent of OpenGL.

I also want to add that when performing CPU intensive tasks it's better to use C/C++/Rust instead of Java/Kotlin. Because the first runs on the CPU directly while the latter doesn't. This saves battery and is faster. See JNI tips.

zomega
  • 1,538
  • 8
  • 26
  • For accurate emulation you'd want to emulate the NES PPU on a per-pixel basis. Or at the very least on a per-scanline basis. But the actual rendering of the end result should of course be done in a way that is reasonably efficient on the host machine (e.g. drawing into some off-screen buffer, like a texture, that you can then display all at once). – Michael Mar 15 '23 at 11:52
  • @Michael OpenGL allows you to add effects like scan lines. For example there are OpenGL shaders. – zomega Mar 15 '23 at 14:27
  • I wasn't talking about post-processing effects like CRT scanline simulation. I was talking about how to emulate the NES PPU's output. The contents of PPU registers can be modified between two scanlines being output by the PPU, or sometimes even in the middle of a scanline. And many NES games use graphics ROM rather than graphics RAM, and thereby had the ability to instantly swap large amounts of tile data in the middle of a frame. – Michael Mar 15 '23 at 14:34
  • @Michael You say a good emulator emulates the NES PPU on a per-pixel basis. Is this done on the CPU or on the GPU using an OpenGL shader for example? – zomega Mar 15 '23 at 14:50
  • I've never appempted writing an NES PPU emulator as fragment/pixel shaders, so I don't know whether that's feasible/efficient. I'm not arguing against the use of a GLSurfaceView to display the emulated PPU's output on the host machine's screen; that's probably a good idea. I'm just saying that when it comes to generating the raw output (i.e. the 256x224 pixel bitmap/texture/whatever), PPU registers or PPU memory mapping may have changed in the time between two adjacent pixels being plotted. So you need to take that into account in the emulation. – Michael Mar 15 '23 at 18:13