这里写自定义目录标题

  • 模拟屏幕
  • 光栅图形
    • GPU 帧时序

在本系列的前几部分中,已经布置了 GameBoy 模拟器的结构,并达到了可以加载游戏 ROM 的程度,并由模拟的 CPU 逐步完成。通过将仿真处理器连接到内存映射结构,现在可以将外围设备连接到系统。GameBoy 和任何游戏控制台使用的主要外围设备之一是图形处理器 (GPU):它是控制台的主要输出方法,处理器的大部分工作都在为 GPU 生成图形。

模拟屏幕

任天堂对 GameBoy 的内部名称是“点阵游戏”;它的显示器是尺寸为 160x144 的像素 LCD。如果将 LCD 中的每个像素都当作 HTML5 中的一个像素,则可以直接映射到宽 160 和高 144 的画布。 为了直接寻址 LCD 中的每个像素,可以操纵画布的内容作为“帧缓冲区”:包含整个画布的单个内存块,作为一系列 4 字节 RGBA 值。

index.html

<html><head><script type="text/javascript" src="js/fileread.js"></script><script type="text/javascript" src="js/log.js"></script><script type="text/javascript" src="js/gpu.js"></script><script type="text/javascript" src="js/mmu.js"></script><script type="text/javascript" src="js/key.js"></script><script type="text/javascript" src="js/timer.js"></script><script type="text/javascript" src="js/z80.js"></script><script type="text/javascript" src="js/tabs.js"></script><script type="text/javascript" src="js/xhr.js"></script><style type="text/css">
* { margin:0; padding: 0; }
body { padding: 5px; background-color: black; color: white; font-family:Arial, Helvetica, sans-serif; font-size:0.82em; }
#out { height:144px; width:160px; border: 1px solid white; float:left; margin:0 5px 0 0; }
#msg { margin: 5px; font-family: sans-serif; font-size: 0.82em; }
div.tab { height:124px; width:320px; border:1px solid white; margin:20px 5px 5px 165px; overflow:auto; }
ul.ops { float:left; list-style:none inside; }
ul.ops li { cursor:pointer; }
table#reg { font-size:11px; font-family:Lucida Console, Bitstream Vera Sans Mono, monospace; line-height:1em; }
table#reg td.regname { text-align:right; padding-left:1em; }
ul.tablist { list-style: none inside; position:relative; bottom:-1px; }
ul.tablist li { display: block; float: left; background: #444; border-top: 1px solid #444; border-bottom: 1px solid white; padding: 3px 0.5em; margin-right: 2px; cursor: pointer; font-size:9px; }
ul.tablist li.tab_hi { border-left: 1px solid white; border-right: 1px solid white; border-top: 1px solid white; border-bottom: 1px solid black; background: black; }
p#op_load { margin-left: 165px; }
input { background:black; color:white; border:1px solid white; width:5em; }
input#file { width:10em; }
div#tilectrl { float:left; margin:1em;}
div#tilepixels { width:96px; height:96px; border:1px solid white; float:left; margin:1em; }
div#tilepixels div { width:12px; height:12px; float:left; }
div.subcanv { width:160px; float:left; }
p.fps { float:right; text-align:right; }</style></head><body><div id="out"><canvas id="screen" width="160" height="144"></canvas></div><ul class="tablist" id="tablist_debug"><li rel="tab_message">Messages</li><li rel="tab_registers">Debugger</li><li rel="tab_tile">Tile View</li></ul><div class="tab" id="tab_message"><div id="msg"></div></div><div class="tab" id="tab_registers"><table id="reg"><tr><td class="regname">A</td><td class="reg" rel="a"></td><td class="regname">LCDC</td><td class="io" rel="40"></td><td class="regname">JOYP</td><td class="io" rel="00"></td></tr><tr><td class="regname">B</td><td class="reg" rel="b"></td><td class="regname">STAT</td><td class="io" rel="41"></td></tr><tr><td class="regname">C</td><td class="reg" rel="c"></td><td class="regname">SCY</td><td class="io" rel="42"></td><td class="regname">DIV</td><td class="io" rel="04"></td></tr><tr><td class="regname">D</td><td class=reg" rel="d"></td><td class="regname">SCX</td><td class="io" rel="43"></td><td class="regname">TIMA</td><td class="io" rel="05"></td></tr><tr><td class="regname">E</td><td class="reg" rel="e"></td><td class="regname">LY</td><td class="io" rel="44"></td><td class="regname">TMA</td><td class="io" rel="06"></td></tr><tr><td class="regname">HL</td><td class="reg" rel="hl"></td><td class="regname">LYC</td><td class="io" rel="45"></td><td class="regname">TCA</td><td class="io" rel="07"></td></tr><tr><td class="regname">PC</td><td class="reg" rel="pc"></td><td class="regname">IE</td><td class="io" rel="ff"></td></tr><tr><td class="regname">SP</td><td class="reg" rel="sp"></td><td class="regname">IF</td><td class="io" rel="0f"></td></tr><tr><td class="regname">F</td><td class="reg" rel="f"></td></tr></table></div><div class="tab" id="tab_tile"><div id="tilectrl"><input type="text" id="tilenum" value="0"><br><ul class="ops"><li id="tileprev">Prev</li><li id="tilenext">Next</li></ul></div><div id="tilepixels"></div></div><div class="subcanv"><ul class="ops"><li id="op_reset">Reset</li><li id="op_run">Run</li><li id="op_step">Step</li></ul><p class="fps"><span id="fps">0</span> fps</p></div><p id="op_load">Load <input type="text" id="file" value="tests/ttt.gb">Break at <input type="text" id="breakpoint"></p><script type="text/javascript">
jsGB = {run_interval: 0,trace: '',frame: function() {var fclock = Z80._clock.m+17556;var brk = document.getElementById('breakpoint').value;var t0 = new Date();do {if(Z80._halt) Z80._r.m=1;else{//  Z80._r.r = (Z80._r.r+1) & 127;Z80._map[MMU.rb(Z80._r.pc++)]();Z80._r.pc &= 65535;}if(Z80._r.ime && MMU._ie && MMU._if){Z80._halt=0; Z80._r.ime=0;var ifired = MMU._ie & MMU._if;if(ifired&1) { MMU._if &= 0xFE; Z80._ops.RST40(); }else if(ifired&2) { MMU._if &= 0xFD; Z80._ops.RST48(); }else if(ifired&4) { MMU._if &= 0xFB; Z80._ops.RST50(); }else if(ifired&8) { MMU._if &= 0xF7; Z80._ops.RST58(); }else if(ifired&16) { MMU._if &= 0xEF; Z80._ops.RST60(); }else { Z80._r.ime=1; }}//jsGB.dbgtrace();Z80._clock.m += Z80._r.m;GPU.checkline();TIMER.inc();if((brk && parseInt(brk,16)==Z80._r.pc) || Z80._stop){jsGB.pause();break;}} while(Z80._clock.m < fclock);var t1 = new Date();document.getElementById('fps').innerHTML=Math.round(10000/(t1-t0))/10;},reset: function() {LOG.reset(); GPU.reset(); MMU.reset(); Z80.reset(); KEY.reset(); TIMER.reset();Z80._r.pc=0x100;MMU._inbios=0;Z80._r.sp=0xFFFE;Z80._r.hl=0x014D;Z80._r.c=0x13;Z80._r.e=0xD8;Z80._r.a=1;MMU.load(document.getElementById('file').value);document.getElementById('op_reset').onclick=jsGB.reset;document.getElementById('op_run').onclick=jsGB.run;document.getElementById('op_run').innerHTML='Run';document.getElementById('op_step').onclick=jsGB.step;document.getElementById('tilepixels').innerHTML='';var tp = document.createElement('div');var x;for(var i=0; i<64; i++){document.getElementById('tilepixels').appendChild(tp);tp = tp.cloneNode(false);}document.getElementById('tilenum').onupdate=jsGB.dbgtile();document.getElementById('tileprev').onclick=function(){var t=parseInt(document.getElementById('tilenum').value); t--; if(t<0) t=383;document.getElementById('tilenum').value=t.toString();jsGB.dbgtile();};document.getElementById('tilenext').onclick=function(){var t=parseInt(document.getElementById('tilenum').value); t++; if(t>383) t=0;document.getElementById('tilenum').value=t.toString();jsGB.dbgtile();};jsGB.dbgupdate();jsGB.dbgtile();jsGB.trace = '';tabMagic.init();jsGB.pause();LOG.out('MAIN', 'Reset.');},run: function() {Z80._stop = 0;jsGB.run_interval = setInterval(jsGB.frame,1);document.getElementById('op_run').innerHTML = 'Pause';document.getElementById('op_run').onclick = jsGB.pause;},pause: function() {clearInterval(jsGB.run_interval);Z80._stop = 1;jsGB.dbgupdate();document.getElementById('op_run').innerHTML = 'Run';document.getElementById('op_run').onclick = jsGB.run;//XHR.connect('/log.php', {trace:jsGB.trace}, {success:function(x){}});},dbgupdate: function() {var t = document.getElementById('reg').getElementsByTagName('td');var x,j,k;for(var i=0; i<t.length; i++){if(t[i].className=='reg'){switch(t[i].getAttribute('rel')){case 'a': case 'b': case 'c': case 'd': case 'e':eval('x=Z80._r.'+t[i].getAttribute('rel')+'.toString(16);if(x.length==1)x="0"+x;');break;case 'pc': case 'sp':eval('x=Z80._r.'+t[i].getAttribute('rel')+'.toString(16);if(x.length<4){p="";for(j=4;j>x.length;j--)p+="0";x=p+x;}');break;case 'hl':k = (Z80._r.h<<8)+Z80._r.l;x = k.toString(16); if(x.length<4){p="";for(j=4;j>x.length;j--)p+="0";x=p+x;}break;case 'f':x = (Z80._r.f>>4).toString(2);if(x.length<4){p="";for(j=4;j>x.length;j--)p+="0";x=p+x;}break;}t[i].innerHTML = x;}else if(t[i].className=='io'){j = parseInt(t[i].getAttribute('rel'),16);x = MMU.rb(0xFF00+j).toString(16);if(typeof(x) != 'undefined'){if(x.length==1) x='0'+x;t[i].innerHTML = x;}}}},dbgtrace: function() {var a = Z80._r.a.toString(16); if(a.length==1) a='0'+a;var b = Z80._r.b.toString(16); if(b.length==1) b='0'+b;var c = Z80._r.c.toString(16); if(c.length==1) c='0'+c;var d = Z80._r.d.toString(16); if(d.length==1) d='0'+d;var e = Z80._r.e.toString(16); if(e.length==1) e='0'+e;var f = Z80._r.f.toString(16); if(f.length==1) f='0'+f;var h = Z80._r.h.toString(16); if(h.length==1) h='0'+h;var l = Z80._r.l.toString(16); if(l.length==1) l='0'+l;var pc = Z80._r.pc.toString(16); if(pc.length<4) { p=''; for(i=4;i>pc.length;i--) p+='0'; pc=p+pc; }var sp = Z80._r.sp.toString(16); if(sp.length<4) { p=''; for(i=4;i>sp.length;i--) p+='0'; sp=p+sp; }jsGB.trace +=("A"+a+"/B"+b+"/C"+c+"/D"+d+"/E"+e+"/F"+f+"/H"+h+"/L"+l+"/PC"+pc+"/SP"+sp+"\n");},dbgtile: function() {var tn = parseInt(document.getElementById('tilenum').value);var t = GPU._tilemap[tn];var c = ['#ffffff','#c0c0c0','#606060','#000000'];var d = document.getElementById('tilepixels').getElementsByTagName('div');for(var y=0;y<8;y++)for(var x=0;x<8;x++)d[y*8+x].style.backgroundColor=c[t[y][x]];},step: function() {if(Z80._r.ime && MMU._ie && MMU._if){Z80._halt=0; Z80._r.ime=0;if((MMU._ie&1) && (MMU._if&1)){MMU._if &= 0xFE; Z80._ops.RST40();}}else{if(Z80._halt) { Z80._r.m=1; }else{Z80._r.r = (Z80._r.r+1) & 127;Z80._map[MMU.rb(Z80._r.pc++)]();Z80._r.pc &= 65535;}}Z80._clock.m += Z80._r.m; Z80._clock.t += (Z80._r.m*4);GPU.checkline();if(Z80._stop){jsGB.pause();}jsGB.dbgupdate();}
};window.onload = jsGB.reset;
window.onkeydown = KEY.keydown;
window.onkeyup = KEY.keyup;</script></body>
</html>

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);elseGPU._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 startGPU._bgtilebase = 0x0000;GPU._bgmapbase = 0x1800;GPU._wintilebase = 0x1800;LOG.out('GPU', 'Reset.');},checkline: function() {GPU._modeclocks += Z80._r.m;switch(GPU._linemode){// In hblankcase 0:if(GPU._modeclocks >= 51){// End of hblank for last scanline; render screenif(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 vblankcase 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 modecase 2:if(GPU._modeclocks >= 20){GPU._modeclocks = 0;GPU._linemode = 3;}break;// In VRAM-read modecase 3:// Render scanline at end of allotted timeif(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)];elsetilerow = 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>>1)&7;var sx;for(var x=0;x<8;x++){sx=1<<(7-x);GPU._tilemap[tile][y][x] = ((GPU._vram[saddr]&sx)?1:0) | ((GPU._vram[saddr+1]&sx)?2:0);}},updateoam: function(addr,val) {addr-=0xFE00;var obj=addr>>2;if(obj<40){switch(addr&3){case 0: GPU._objdata[obj].y=val-16; break;case 1: GPU._objdata[obj].x=val-8; break;case 2:if(GPU._objsize) GPU._objdata[obj].tile = (val&0xFE);else GPU._objdata[obj].tile = val;break;case 3:GPU._objdata[obj].palette = (val&0x10)?1:0;GPU._objdata[obj].xflip = (val&0x20)?1:0;GPU._objdata[obj].yflip = (val&0x40)?1:0;GPU._objdata[obj].prio = (val&0x80)?1:0;break;}}GPU._objdatasorted = GPU._objdata;GPU._objdatasorted.sort(function(a,b){if(a.x>b.x) return -1;if(a.num>b.num) return -1;});},rb: function(addr) {var gaddr = addr-0xFF40;switch(gaddr){case 0:return (GPU._lcdon?0x80:0)|((GPU._bgtilebase==0x0000)?0x10:0)|((GPU._bgmapbase==0x1C00)?0x08:0)|(GPU._objsize?0x04:0)|(GPU._objon?0x02:0)|(GPU._bgon?0x01:0);case 1:return (GPU._curline==GPU._raster?4:0)|GPU._linemode;case 2:return GPU._yscrl;case 3:return GPU._xscrl;case 4:return GPU._curline;case 5:return GPU._raster;default:return GPU._reg[gaddr];}},wb: function(addr,val) {var gaddr = addr-0xFF40;GPU._reg[gaddr] = val;switch(gaddr){case 0:GPU._lcdon = (val&0x80)?1:0;GPU._bgtilebase = (val&0x10)?0x0000:0x0800;GPU._bgmapbase = (val&0x08)?0x1C00:0x1800;GPU._objsize = (val&0x04)?1:0;GPU._objon = (val&0x02)?1:0;GPU._bgon = (val&0x01)?1:0;break;case 2:GPU._yscrl = val;break;case 3:GPU._xscrl = val;break;case 5:GPU._raster = val;// OAM DMAcase 6:var v;for(var i=0; i<160; i++){v = MMU.rb((val<<8)+i);GPU._oam[i] = v;GPU.updateoam(0xFE00+i, v);}break;// BG palette mappingcase 7:for(var i=0;i<4;i++){switch((val>>(i*2))&3){case 0: GPU._palette.bg[i] = 255; break;case 1: GPU._palette.bg[i] = 192; break;case 2: GPU._palette.bg[i] = 96; break;case 3: GPU._palette.bg[i] = 0; break;}}break;// OBJ0 palette mappingcase 8:for(var i=0;i<4;i++){switch((val>>(i*2))&3){case 0: GPU._palette.obj0[i] = 255; break;case 1: GPU._palette.obj0[i] = 192; break;case 2: GPU._palette.obj0[i] = 96; break;case 3: GPU._palette.obj0[i] = 0; break;}}break;// OBJ1 palette mappingcase 9:for(var i=0;i<4;i++){switch((val>>(i*2))&3){case 0: GPU._palette.obj1[i] = 255; break;case 1: GPU._palette.obj1[i] = 192; break;case 2: GPU._palette.obj1[i] = 96; break;case 3: GPU._palette.obj1[i] = 0; break;}}break;}}
};

一旦为屏幕数据分配了一块内存,就可以通过将 RGBA 分量写入块中该像素位置的四个值来设置单个像素的颜色;像素位置可由公式确定y * 160 + x。

光栅图形

在画布就位以接收 GameBoy 的图形输出后,下一步是模拟图形的制作。原来的GameBoy硬件在时序上模拟了阴极射线管(CRT):在CRT中,电子束逐行扫描屏幕(从左到右,从上到下),扫描结束后扫描过程返回到屏幕顶部。

可以看出,CRT 需要更多的时间来绘制扫描线,而不是简单地在有问题的像素上运行:需要一个“水平消隐”期,使光束从一行的末尾移动到下一行的开头. 类似地,每一帧的结束意味着一个“垂直消隐”期,而光束则返回到左上角。由于光束必须在垂直消隐中进一步移动,因此该时间段通常比水平消隐时间长得多。

同样,GameBoy 显示器呈现水平和垂直消隐期。此外,扫描线本身所花费的时间分为两部分:GPU 在访问视频内存和访问精灵属性内存之间切换,同时绘制扫描线。出于此仿真的目的,这两个部分是不同的,并且相互遵循。下表说明了 GPU 在每个周期内停留的时间,以运行在 4194304 Hz 的 CPU 的 T 时钟表示。

GPU 帧时序

时期 GPU 模式编号 花费的时间(时钟)
Scanline(访问OAM) 2 80
扫描线(访问 VRAM) 3 172
水平空白 0 204
一行(扫描和空白) 456
垂直空白 1 4560(10行)
全帧(扫描和空白) 70224

为了保持这些与仿真 CPU 相关的时序,必须存在时序更新函数,该函数在每条指令执行后都会被调用。

Z80.js:调度器

while (true) {Z80._map[MMU.rb(Z80._r.pc++)]();Z80._r.pc &= 65535;Z80._clock.m += Z80._r.m;Z80._clock.t += Z80._r.t;GPU.step();
}

GPU.js:时钟步长

 _mode: 0,_modeclock: 0,_line: 0,step: function(){GPU._modeclock += Z80._r.t;switch(GPU._mode){// OAM read mode, scanline activecase 2:if(GPU._modeclock >= 80){// Enter scanline mode 3GPU._modeclock = 0;GPU._mode = 3;}break;// VRAM read mode, scanline active// Treat end of mode 3 as end of scanlinecase 3:if(GPU._modeclock >= 172){// Enter hblankGPU._modeclock = 0;GPU._mode = 0;// Write a scanline to the framebufferGPU.renderscan();}break;// Hblank// After the last hblank, push the screen data to canvascase 0:if(GPU._modeclock >= 204){GPU._modeclock = 0;GPU._line++;if(GPU._line == 143){// Enter vblankGPU._mode = 1;GPU._canvas.putImageData(GPU._scrn, 0, 0);}else{GPU._mode = 2;}}break;// Vblank (10 lines)case 1:if(GPU._modeclock >= 456){GPU._modeclock = 0;GPU._line++;if(GPU._line > 153){// Restart scanning modesGPU._mode = 2;GPU._line = 0;}}break;}}

在上面的代码中,GPU 的计时已经建立,但 GPU 的工作还没有到位:renderscan 是工作发生的地方。在本系列的下一部分中,将研究 GameBoy 的背景图形系统背后的概念,并将代码放入渲染函数中以模拟它们。

Game boy模拟器(3):GPU的时序相关推荐

  1. 告别CPU,加速100-1000倍!只用GPU就能完成物理模拟和强化学习训练

    编译 | 王晔 校对 | 青暮 Isaac Gym由英伟达开发,通过直接将数据从物理缓存传递到PyTorch张量进行通信,可以端到端地在GPU上实现物理模拟和神经网络策略训练,无需CPU.Isaac ...

  2. android 模拟器 3D 开发环境配置

    使用HAXM 与 KVM 给emulator加速 Android emulator 运行很慢,应该使用虚拟机加速, Windows 下 Android SDK Manager中可以安装 HAXM, 安 ...

  3. mysql8.0其他机器访问_量子公司重大突破,量子机器学习实用化进程加速

    编辑:Yan  Ding   校对:Peiyong Wang 近日,著名的量子计算公司QC Ware宣布,其在量子机器学习(QML)方面取得多项重大突破,该突破将以更快的速度推动量子机器学习的发展. ...

  4. PIC单片机开发工具

    MPLAB简介 MPLAB 集成开发环境(IDE)是一个综合的编辑器.项目管理器和设计平台, 提供以下功能:使用内置的编辑器创建和编辑源代码. 汇编.编译和链接源代码, 通过使用内置的软件模拟器观察程 ...

  5. 搭建cocos2d-x-android环境 Windows XP3 + Eclipse + NDKR7(或ndkr7b)+COCOS2DX(没有用到cygwin和minigw)

    版本: Windows XP3 JDK1.6 Eclipse 3.7.1 (需要C++插件CDT) ADT 16.0.1 NDK7 (或是NDKR7B) cocos2d-1.0.1-x-0.11.0. ...

  6. iOS ffmpeg+OpenGL播放yuv+openAL 快放 慢放 视频播放器

    由于老版本的ffmpeg一些使用方法将要废弃如streams[videoStream]->codec这种方式查找解码器就不能用了,再使用就会报警告,或者报错,这里使用新版ffmpeg接口制作播放 ...

  7. win7下android+Cocos2d-x 2.23 环境搭建

    我自己是按照这篇博客学习搭建的 win32的环境就不在描述了 感谢作者 之所以自己在写一篇是为了加强记忆 共享资源 原作者博客:http://blog.csdn.net/aa4790139/artic ...

  8. MonkeyRunner自动化测试学习

    jdk的下载安装,环境变量配置 python安装 Eclipse下载 Eclipse安装ADT插件 Android Sdk安装和环境变量配置 手动关联Android Sdk 没有Android图标 使 ...

  9. 关于工业级GPU C-model所使用的性能模拟器(preformance simulator)

    http://www.opengpu.org/forum.php?mod=viewthread&tid=2935 关于工业级GPU C-model所使用的性能模拟器(preformance s ...

  10. Android 模拟器 GPU ON

    Android模拟器 3D 加速架构 运行 Android 模拟器的有个 GPU 开关,当打开 GPU 开关 3D 加速功能将转给 HOST的 GPU, 否则就是用 Soft 3D(CPU 执行 GP ...

最新文章

  1. 电脑屏幕变黄如何调整_如何调整电脑屏幕比例
  2. 安装Ubunutu音频视频库
  3. 事业编,突然接到换岗通知,作为个人能怎么办?能拒绝换岗吗?拒绝的后果是什么?
  4. 38、linux shell常用函数,nice
  5. 每日一技|活锁,也许你需要了解一下
  6. vue+video.js实现前端视频流(hls、MP4、flv)
  7. windows 2008 配置php_Windows 2008 R2 下IIS7.5+PHP5.2环境配置(FastCgi设置)
  8. STAP旁瓣干扰抑制与干扰对抗仿真
  9. FC经典游戏600合集for mac(小霸王游戏) 中文版
  10. 解决外接显示屏后CPU占用率过高问题
  11. mysql 复合索引 悲观锁_对MySQL索引、锁及事务的简单分析
  12. OpenCV入门(C++/Python)-使用OpenCV裁剪图像(四)
  13. leetcode打家劫社简单实现--python
  14. sso php 实现,Jasny-SSO
  15. Katalon自动化测试
  16. Minecraft 1.16.5 生化8 模组(重制版) 自行火炮登场
  17. 关于如何向老板提涨工资
  18. 美杀人魔BTK与警方玩“老鼠戏猫”游戏31年,却栽在一个小小的word文档上面!...
  19. 异常处理——NullPointerException
  20. 疯狂坦克 高级教程(一)

热门文章

  1. 将win7笔记本电脑变身WiFi热点,让手机、笔记本共享上网
  2. vue项目使用mand mobile check选择项组点击选中,选中的列表延迟一位问题
  3. 数据库复杂查询,左联右联 聚合 计数 时间查询等,持续更新
  4. 待支付取件费用是什么意思_菜鸟裹裹待支付怎么取消
  5. 【递归入门】组合的输出
  6. 【生活】驾照C1-科二手册
  7. SQL自动审核工具archer
  8. mysql engine ndb_ndbcluster引擎表同步到innodb引擎报错Error 'Unknown storage engine 'ndbcluster'...
  9. 云原生的进一步具象化
  10. 光学系统像差的计算机模拟实验报告,RLE-ME01-光学系统像差测量实验-实验讲义要点.doc...