Game boy模拟器:图形

Posted 妇男主人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Game boy模拟器:图形相关的知识,希望对你有一定的参考价值。

将 GameBoy 模拟器的形状放在一起,并在 CPU 和图形处理器之间建立了时序。画布已被初始化并准备好由模拟的 GameBoy 绘制图形;GPU 仿真现在具有结构,但仍无法将图形渲染到帧缓冲区。为了让仿真渲染图形,必须简要检查 GameBoy 图形背后的概念。

概述

就像那个时代的大多数游戏机一样,GameBoy 没有足够的内存来允许将直接帧缓冲区保存在内存中。取而代之的是,使用了一个平铺系统:一组小位图保存在内存中,并对这些位图进行引用来构建地图。这个系统的先天优势是一个图块可以通过地图重复使用。

GameBoy 的图块图形系统以 8x8 像素的图块运行,一张地图可以使用 256 个独特的图块;内存中可以保存两张 32x32 图块的地图,其中一张可以一次用于显示。GameBoy 内存中有 384 个图块的空间,因此其中一半在地图之间共享:一张地图使用 0 到 255 之间的图块编号,另一张使用 -128 到 127 之间的数字作为其图块。

在视频内存中,瓦片数据和地图的布局如下运行。

显存的布局

区域用法
8000-87FFTile set #1: tiles 0-127
8800-8FFFTile set #1: tiles 128-255 Tile set #0: tiles -1 to -128
9000-97FFTile set #0: tiles 0-127
9800-9BFFTile map #0
9C00-9FFFTile map #1

定义背景后,其地图和图块数据会相互作用以生成图形显示:

背景映射

如前所述,背景图是 32x32 的图块;这是 256 x 256 像素。GameBoy 的显示为 160x144 像素,因此背景可以相对于屏幕移动。GPU 通过在背景中定义一个对应于屏幕左上角的点来实现这一点:通过在帧之间移动这个点,背景在屏幕上滚动。为此,左上角的定义由两个 GPU 寄存器保存:Scroll X 和 Scroll Y。

背景滚动寄存器

调色板

GameBoy 通常被描述为单色机器,只能显示黑白。这并不完全正确:GameBoy 还可以处理浅灰色和深灰色,总共四种颜色。在图块数据中表示这四种颜色中的一种需要两位,因此图块数据集中的每个图块都以 (8x8x2) 位或 16 个字节表示。

GameBoy 背景的另一个复杂之处是在瓷砖数据和最终显示之间插入了一个调色板:瓷砖像素的四个可能值中的每一个都可以对应于四种颜色中的任何一种。这主要用于让瓷砖集轻松更改颜色;例如,如果一组拼贴对应于英文字母,则可以通过更改调色板来构建反视频版本,而不是占用拼贴组的另一部分。通过更改背景调色板 GPU 寄存器的值,四个调色板条目同时更新;使用的颜色参考和寄存器的结构如下所示。

颜色参考值

像素仿真颜色
0关闭[255, 255, 255]
133% 开启[192, 192, 192]
266% 开启[96, 96, 96]
3全开启[0, 0, 0]

实现:瓦片数据

如上所述,瓦片数据集中的每个像素由两位表示:当在地图中引用瓦片时,这些位由 GPU 读取,穿过调色板并推送到屏幕。GPU 的硬件是这样接线的,即可以同时访问整行瓦片,并且像素通过运行位循环。唯一的问题是瓦片的一行是两个字节:由此产生了稍微复杂的位存储方案,其中每个像素的低位保存在一个字节中,而高位保存在另一个字节中。

平铺数据位图结构

由于 javascript 不是快速操作位图结构的理想选择,因此处理 tile 数据集最省时的方法是在视频内存旁边维护一个内部数据集,并具有更扩展的视图,其中每个像素的值已预先确定计算。为了准确反映瓦片数据集,对视频 RAM 的任何写入都必须触发更新 GPU 内部瓦片数据的函数。

GPU.js的源码

GPU = {
  _vram: [],
  _oam: [],
  _reg: [],
  _tilemap: [],
  _objdata: [],
  _objdatasorted: [],
  _palette: {'bg':[], 'obj0':[], 'obj1':[]},
  _scanrow: [],

  _curline: 0,
  _curscan: 0,
  _linemode: 0,
  _modeclocks: 0,

  _yscrl: 0,
  _xscrl: 0,
  _raster: 0,
  _ints: 0,
  
  _lcdon: 0,
  _bgon: 0,
  _objon: 0,
  _winon: 0,

  _objsize: 0,

  _bgtilebase: 0x0000,
  _bgmapbase: 0x1800,
  _wintilebase: 0x1800,

  reset: function() {
    for(var i=0; i<8192; i++) {
      GPU._vram[i] = 0;
    }
    for(i=0; i<160; i++) {
      GPU._oam[i] = 0;
    }
    for(i=0; i<4; i++) {
      GPU._palette.bg[i] = 255;
      GPU._palette.obj0[i] = 255;
      GPU._palette.obj1[i] = 255;
    }
    for(i=0;i<512;i++)
    {
      GPU._tilemap[i] = [];
      for(j=0;j<8;j++)
      {
        GPU._tilemap[i][j] = [];
        for(k=0;k<8;k++)
        {
          GPU._tilemap[i][j][k] = 0;
        }
      }
    }

    LOG.out('GPU', 'Initialising screen.');
    var c = document.getElementById('screen');
    if(c && c.getContext)
    {
      GPU._canvas = c.getContext('2d');
      if(!GPU._canvas)
      {
        throw new Error('GPU: Canvas context could not be created.');
      }
      else
      {
        if(GPU._canvas.createImageData)
          GPU._scrn = GPU._canvas.createImageData(160,144);
        else if(GPU._canvas.getImageData)
          GPU._scrn = GPU._canvas.getImageData(0,0,160,144);
        else
          GPU._scrn = {'width':160, 'height':144, 'data':new Array(160*144*4)};

        for(i=0; i<GPU._scrn.data.length; i++)
          GPU._scrn.data[i]=255;

        GPU._canvas.putImageData(GPU._scrn, 0,0);
      }
    }

    GPU._curline=0;
    GPU._curscan=0;
    GPU._linemode=2;
    GPU._modeclocks=0;
    GPU._yscrl=0;
    GPU._xscrl=0;
    GPU._raster=0;
    GPU._ints = 0;

    GPU._lcdon = 0;
    GPU._bgon = 0;
    GPU._objon = 0;
    GPU._winon = 0;

    GPU._objsize = 0;
    for(i=0; i<160; i++) GPU._scanrow[i] = 0;

    for(i=0; i<40; i++)
    {
      GPU._objdata[i] = {'y':-16, 'x':-8, 'tile':0, 'palette':0, 'yflip':0, 'xflip':0, 'prio':0, 'num':i};
    }

    // Set to values expected by Bios, to start
    GPU._bgtilebase = 0x0000;
    GPU._bgmapbase = 0x1800;
    GPU._wintilebase = 0x1800;

    LOG.out('GPU', 'Reset.');
  },

  checkline: function() {
    GPU._modeclocks += Z80._r.m;
    switch(GPU._linemode)
    {
      // In hblank
      case 0:
        if(GPU._modeclocks >= 51)
        {
          // End of hblank for last scanline; render screen
          if(GPU._curline == 143)
          {
            GPU._linemode = 1;
            GPU._canvas.putImageData(GPU._scrn, 0,0);
            MMU._if |= 1;
          }
          else
          {
            GPU._linemode = 2;
          }
          GPU._curline++;
	  GPU._curscan += 640;
          GPU._modeclocks=0;
        }
        break;

      // In vblank
      case 1:
        if(GPU._modeclocks >= 114)
        {
          GPU._modeclocks = 0;
          GPU._curline++;
          if(GPU._curline > 153)
          {
            GPU._curline = 0;
	    GPU._curscan = 0;
            GPU._linemode = 2;
          }
        }
        break;

      // In OAM-read mode
      case 2:
        if(GPU._modeclocks >= 20)
        {
          GPU._modeclocks = 0;
          GPU._linemode = 3;
        }
        break;

      // In VRAM-read mode
      case 3:
        // Render scanline at end of allotted time
        if(GPU._modeclocks >= 43)
        {
          GPU._modeclocks = 0;
          GPU._linemode = 0;
          if(GPU._lcdon)
          {
            if(GPU._bgon)
            {
              var linebase = GPU._curscan;
              var mapbase = GPU._bgmapbase + ((((GPU._curline+GPU._yscrl)&255)>>3)<<5);
              var y = (GPU._curline+GPU._yscrl)&7;
              var x = GPU._xscrl&7;
              var t = (GPU._xscrl>>3)&31;
              var pixel;
              var w=160;

              if(GPU._bgtilebase)
              {
	        var tile = GPU._vram[mapbase+t];
		if(tile<128) tile=256+tile;
                var tilerow = GPU._tilemap[tile][y];
                do
                {
		  GPU._scanrow[160-x] = tilerow[x];
                  GPU._scrn.data[linebase+3] = GPU._palette.bg[tilerow[x]];
                  x++;
                  if(x==8) { t=(t+1)&31; x=0; tile=GPU._vram[mapbase+t]; if(tile<128) tile=256+tile; tilerow = GPU._tilemap[tile][y]; }
                  linebase+=4;
                } while(--w);
              }
              else
              {
                var tilerow=GPU._tilemap[GPU._vram[mapbase+t]][y];
                do
                {
		  GPU._scanrow[160-x] = tilerow[x];
                  GPU._scrn.data[linebase+3] = GPU._palette.bg[tilerow[x]];
                  x++;
                  if(x==8) { t=(t+1)&31; x=0; tilerow=GPU._tilemap[GPU._vram[mapbase+t]][y]; }
                  linebase+=4;
                } while(--w);
	      }
            }
            if(GPU._objon)
            {
              var cnt = 0;
              if(GPU._objsize)
              {
                for(var i=0; i<40; i++)
                {
                }
              }
              else
              {
                var tilerow;
                var obj;
                var pal;
                var pixel;
                var x;
                var linebase = GPU._curscan;
                for(var i=0; i<40; i++)
                {
                  obj = GPU._objdatasorted[i];
                  if(obj.y <= GPU._curline && (obj.y+8) > GPU._curline)
                  {
                    if(obj.yflip)
                      tilerow = GPU._tilemap[obj.tile][7-(GPU._curline-obj.y)];
                    else
                      tilerow = GPU._tilemap[obj.tile][GPU._curline-obj.y];

                    if(obj.palette) pal=GPU._palette.obj1;
                    else pal=GPU._palette.obj0;

                    linebase = (GPU._curline*160+obj.x)*4;
                    if(obj.xflip)
                    {
                      for(x=0; x<8; x++)
                      {
                        if(obj.x+x >=0 && obj.x+x < 160)
                        {
                          if(tilerow[7-x] && (obj.prio || !GPU._scanrow[x]))
                          {
                            GPU._scrn.data[linebase+3] = pal[tilerow[7-x]];
                          }
                        }
                        linebase+=4;
                      }
                    }
                    else
                    {
                      for(x=0; x<8; x++)
                      {
                        if(obj.x+x >=0 && obj.x+x < 160)
                        {
                          if(tilerow[x] && (obj.prio || !GPU._scanrow[x]))
                          {
                            GPU._scrn.data[linebase+3] = pal[tilerow[x]];
                          }
                        }
                        linebase+=4;
                      }
                    }
                    cnt++; if(cnt>10) break;
                  }
                }
              }
            }
          }
        }
        break;
    }
  },

  updatetile: function(addr,val) {
    var saddr = addr;
    if(addr&1) { saddr--; addr--; }
    var tile = (addr>>4)&511;
    var y = (addr>&

以上是关于Game boy模拟器:图形的主要内容,如果未能解决你的问题,请参考以下文章

Game boy模拟器:GPU的时序

Game boy模拟器:输入

Game boy模拟器:精灵

Game boy模拟器:运行内存

Game boy模拟器:内存池

Game boy模拟器:完整的 Z80 内核CPU源码