Game boy模拟器(3):GPU的时序
这里写自定义目录标题
- 模拟屏幕
- 光栅图形
- 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的时序相关推荐
- 告别CPU,加速100-1000倍!只用GPU就能完成物理模拟和强化学习训练
编译 | 王晔 校对 | 青暮 Isaac Gym由英伟达开发,通过直接将数据从物理缓存传递到PyTorch张量进行通信,可以端到端地在GPU上实现物理模拟和神经网络策略训练,无需CPU.Isaac ...
- android 模拟器 3D 开发环境配置
使用HAXM 与 KVM 给emulator加速 Android emulator 运行很慢,应该使用虚拟机加速, Windows 下 Android SDK Manager中可以安装 HAXM, 安 ...
- mysql8.0其他机器访问_量子公司重大突破,量子机器学习实用化进程加速
编辑:Yan Ding 校对:Peiyong Wang 近日,著名的量子计算公司QC Ware宣布,其在量子机器学习(QML)方面取得多项重大突破,该突破将以更快的速度推动量子机器学习的发展. ...
- PIC单片机开发工具
MPLAB简介 MPLAB 集成开发环境(IDE)是一个综合的编辑器.项目管理器和设计平台, 提供以下功能:使用内置的编辑器创建和编辑源代码. 汇编.编译和链接源代码, 通过使用内置的软件模拟器观察程 ...
- 搭建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. ...
- iOS ffmpeg+OpenGL播放yuv+openAL 快放 慢放 视频播放器
由于老版本的ffmpeg一些使用方法将要废弃如streams[videoStream]->codec这种方式查找解码器就不能用了,再使用就会报警告,或者报错,这里使用新版ffmpeg接口制作播放 ...
- win7下android+Cocos2d-x 2.23 环境搭建
我自己是按照这篇博客学习搭建的 win32的环境就不在描述了 感谢作者 之所以自己在写一篇是为了加强记忆 共享资源 原作者博客:http://blog.csdn.net/aa4790139/artic ...
- MonkeyRunner自动化测试学习
jdk的下载安装,环境变量配置 python安装 Eclipse下载 Eclipse安装ADT插件 Android Sdk安装和环境变量配置 手动关联Android Sdk 没有Android图标 使 ...
- 关于工业级GPU C-model所使用的性能模拟器(preformance simulator)
http://www.opengpu.org/forum.php?mod=viewthread&tid=2935 关于工业级GPU C-model所使用的性能模拟器(preformance s ...
- Android 模拟器 GPU ON
Android模拟器 3D 加速架构 运行 Android 模拟器的有个 GPU 开关,当打开 GPU 开关 3D 加速功能将转给 HOST的 GPU, 否则就是用 Soft 3D(CPU 执行 GP ...
最新文章
- 电脑屏幕变黄如何调整_如何调整电脑屏幕比例
- 安装Ubunutu音频视频库
- 事业编,突然接到换岗通知,作为个人能怎么办?能拒绝换岗吗?拒绝的后果是什么?
- 38、linux shell常用函数,nice
- 每日一技|活锁,也许你需要了解一下
- vue+video.js实现前端视频流(hls、MP4、flv)
- windows 2008 配置php_Windows 2008 R2 下IIS7.5+PHP5.2环境配置(FastCgi设置)
- STAP旁瓣干扰抑制与干扰对抗仿真
- FC经典游戏600合集for mac(小霸王游戏) 中文版
- 解决外接显示屏后CPU占用率过高问题
- mysql 复合索引 悲观锁_对MySQL索引、锁及事务的简单分析
- OpenCV入门(C++/Python)-使用OpenCV裁剪图像(四)
- leetcode打家劫社简单实现--python
- sso php 实现,Jasny-SSO
- Katalon自动化测试
- Minecraft 1.16.5 生化8 模组(重制版) 自行火炮登场
- 关于如何向老板提涨工资
- 美杀人魔BTK与警方玩“老鼠戏猫”游戏31年,却栽在一个小小的word文档上面!...
- 异常处理——NullPointerException
- 疯狂坦克 高级教程(一)
热门文章
- 将win7笔记本电脑变身WiFi热点,让手机、笔记本共享上网
- vue项目使用mand mobile check选择项组点击选中,选中的列表延迟一位问题
- 数据库复杂查询,左联右联 聚合 计数 时间查询等,持续更新
- 待支付取件费用是什么意思_菜鸟裹裹待支付怎么取消
- 【递归入门】组合的输出
- 【生活】驾照C1-科二手册
- SQL自动审核工具archer
- mysql engine ndb_ndbcluster引擎表同步到innodb引擎报错Error 'Unknown storage engine 'ndbcluster'...
- 云原生的进一步具象化
- 光学系统像差的计算机模拟实验报告,RLE-ME01-光学系统像差测量实验-实验讲义要点.doc...