Start the NES simulator and open our classic super Mario 1.
Choose tool> viewer> graphic viewer. The following window is displayed.
650) this. width = 650; "src =" http://www.bkjia.com/uploads/allimg/131228/194U64L0-0.png "title =" Capture. PNG "alt =" 141431758.png"/>
Click in the window, and the screen will change.
What is the significance of these images? How does the VirtiaNES simulator display these images?
The above questions are the topic of this blog.
Response Functions
The response function of the menu option "graphic viewer" is:
WNDCMD CMainFrame: OnViewCommand (wnd1_param)
Source Files \ MainFrame. cpp
The search function of VS series IDE should be relatively powerful. I will not talk about the specific line of the function. The code for this response function is as follows:
WNDCMD CMainFrame::OnViewCommand( WNDCMDPARAM ){ if( !Emu.IsRunning() || !Nes ) return; switch( uID ) { case ID_VIEW_PATTERN: if( !m_PatternView.m_hWnd ) { m_PatternView.Create( HWND_DESKTOP ); } ::SetWindowPos( m_PatternView.m_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE ); break; case ID_VIEW_NAMETABLE: if( !m_NameTableView.m_hWnd ) { m_NameTableView.Create( HWND_DESKTOP ); } ::SetWindowPos( m_NameTableView.m_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE ); break; case ID_VIEW_PALETTE: if( !m_PaletteView.m_hWnd ) { m_PaletteView.Create( HWND_DESKTOP ); } ::SetWindowPos( m_PaletteView.m_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE ); break; case ID_VIEW_MEMORY: if( !m_MemoryView.m_hWnd ) { m_MemoryView.Create( HWND_DESKTOP ); } ::SetWindowPos( m_MemoryView.m_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE ); break; default: break; }}
Row 3: The function parameter wnd1_param is a macro definition, which is actually HWND hWnd, UINT uID
HWnd is the handle of the parent window, that is, the main interface. UID is the ID of the menu item.
13-18. The remaining lines of code are ignored for the moment. Because we chose Image Viewer, we came to this branch. If this code is executed for the first time, the window in m_PatternView is not created yet.
After the window in m_PatternView is created successfully, the window is displayed, and the task of this function is finished.
CPatternView
File Source Files/PatternView. cpp Header Files/PatternView. h
M_PatternView in the previous section is an object of the CPatternView class.
In CPatternView, the Create FUNCTION creation window initializes data. The OnCreate function is triggered because a window is created. OnCreate will enable a timer, and then trigger the response function OnTimer.
Okay, OnTimer is the focus of this class. OnTimer constantly reads the latest data and displays it in the window.
The bitmap information header is initialized as follows (in the Create FUNCTION)
m_BitmapHdr.bih.biSize = sizeof(BITMAPINFOHEADER);m_BitmapHdr.bih.biWidth = 128;m_BitmapHdr.bih.biHeight = -256;m_BitmapHdr.bih.biPlanes = 1;m_BitmapHdr.bih.biBitCount = 8;m_BitmapHdr.bih.biCompression = BI_RGB;m_BitmapHdr.bih.biClrUsed = 16;
From this, we can see that the bitmap displayed in the graphic viewer is 128 pixels in width and 256 pixels in height. It uses 8 bits to represent a color, that is, a 256 color image ), 16 colors are used, that is, the color palette has 16 colors ).
Before proceeding, insert some minor issues related to NES. There are two color palette images in the NES file, which are not considered for the moment), namely, the background color palette and the genie color palette. Each color palette occupies 16 bytes. Each byte is an index, representing one of the 256 colors.
Okay. Continue. There is a code in the end of the Create FUNCTION.
DirectDraw.GetPaletteData( m_Palette );
M_Palette is a byte array with a size of 256*4 bytes, which can represent exactly 256 colors. With the name of this variable added, I guess the function of this Code is to save the 256 colors indexed in m_Palette.
It's almost time to study the OnTimer function. The function code is messy together, so the function code can be pasted separately)
LPBYTE pPAL = (m_SelectPal<4)?&BGPAL[m_SelectPal*4]:&SPPAL[(m_SelectPal&3)*4]; m_BitmapHdr.rgb[0] = m_Palette[pPAL[0]]; m_BitmapHdr.rgb[1] = m_Palette[pPAL[1]]; m_BitmapHdr.rgb[2] = m_Palette[pPAL[2]]; m_BitmapHdr.rgb[3] = m_Palette[pPAL[3]];
M_SelectPal is an integer ranging from 0 to 7. After you click the left mouse button, 1 is added. That is why the image above changes when you click the graphic viewer.
One or two lines of code are used to retrieve four color index values from the BGPAL background palette) or the SPPAL genie palette.
3-6 lines of code show that each image of the graphic viewer has only four colors.
M_lpPattern is a member variable of CPatternView. Stores the pixel data of the bitmap to be displayed. Run the following code to assign values to m_lpPattern:
for( INT i = 0; i < 8; i++ ) { if( m_lpBank[i] != PPU_MEM_BANK[i] || PPU_MEM_TYPE[i] == BANKTYPE_CRAM ) { m_lpBank[i] = PPU_MEM_BANK[i]; LPBYTE lpPtn = PPU_MEM_BANK[i]; for( INT p = 0; p < 64; p++ ) { LPBYTE lpScn = &m_lpPattern[i*32*128+(p&15)*8+(p/16)*8*128]; for( INT y = 0; y < 8; y++ ) { BYTE chr_l = lpPtn[y]; BYTE chr_h = lpPtn[y+8]; lpScn[0] = ((chr_h>>6)&2)|((chr_l>>7)&1); lpScn[4] = ((chr_h>>2)&2)|((chr_l>>3)&1); lpScn[1] = ((chr_h>>5)&2)|((chr_l>>6)&1); lpScn[5] = ((chr_h>>1)&2)|((chr_l>>2)&1); lpScn[2] = ((chr_h>>4)&2)|((chr_l>>5)&1); lpScn[6] = ((chr_h>>0)&2)|((chr_l>>1)&1); lpScn[3] = ((chr_h>>3)&2)|((chr_l>>4)&1); lpScn[7] = ((chr_h<<1)&2)|((chr_l>>0)&1); // Next line lpScn+=128; } // Next pattern lpPtn+=16; } } }
PPU_MEM_BANK is a pointer array with a length of 12. The first eight pointers point to the pattern table, and the last four point to the name table or attribute table. Each Pointer Points to a space of 1 K.
To learn more about the graphic table, naming table, or attribute table, you can download the following document to see the graphic processor chapter.
Http://down.51cto.com/data/951473
The following describes my understanding of pattern tables, naming tables, and attribute tables.
The NES game screen is actually composed of 32*30 Tile, each of which has 8*8 pixels. The NES image has only 16 colors, so only four digits are needed to represent a pixel.
How do 8x8 pixels in each Tile be obtained?
The Tile number is saved in the naming table, and the Tile is stored in the pattern table. There are 256 Tile entries in a pattern table, so addressing a Tile requires one byte. The size of a naming table can be calculated. 1 byte * 32*30 = 960 bytes.
The pattern table stores two low pixels in the Tile. As mentioned above, one pattern table stores 256 Tile and one Tile has 8*8 pixels. The size of a pattern table is 2 bits * 256*8*8 = 32768 bits = 4096 bits = 4 kb
The pattern table stores a Tile in 16 bytes, of which 1 and 9 bytes are used to calculate the second row pixel of the Tile, 2 and 10 bytes are used to calculate the second row pixel, and so on. The specific calculation method is as follows: for example, the first pixel in 1st rows, the highest bit in 9th bytes, and the lowest Bit in 1st bytes. That is to say, the last eight bytes are 64-bit high, and the first eight bytes are 64-bit low. Assume that 1st bytes are 01010011 and 9th bytes are 10101111, then the first line of pixel is 2 1 2 1 2 3 3.
The attribute table is small. 1024 bytes of space. The name table occupies 960 bytes, and the remaining 64 bytes are reserved for the attribute table. The Attribute Table and the name table are paired. A byte in the Attribute Table stores 4*4 Tile-level two-digit pixels. Calculate the size of 32*32/4/4 = 64 bytes.
I have to say that this seemingly awkward processing method was quite good in the time when hardware resources were lacking.
Okay, continue back to our code.
This is the most important part of the code of the OnTimer function. To facilitate viewing, paste it again ).
for( INT i = 0; i < 8; i++ ) { if( m_lpBank[i] != PPU_MEM_BANK[i] || PPU_MEM_TYPE[i] == BANKTYPE_CRAM ) { m_lpBank[i] = PPU_MEM_BANK[i]; LPBYTE lpPtn = PPU_MEM_BANK[i]; for( INT p = 0; p < 64; p++ ) { LPBYTE lpScn = &m_lpPattern[i*32*128+(p&15)*8+(p/16)*8*128]; for( INT y = 0; y < 8; y++ ) { BYTE chr_l = lpPtn[y]; BYTE chr_h = lpPtn[y+8]; lpScn[0] = ((chr_h>>6)&2)|((chr_l>>7)&1); lpScn[4] = ((chr_h>>2)&2)|((chr_l>>3)&1); lpScn[1] = ((chr_h>>5)&2)|((chr_l>>6)&1); lpScn[5] = ((chr_h>>1)&2)|((chr_l>>2)&1); lpScn[2] = ((chr_h>>4)&2)|((chr_l>>5)&1); lpScn[6] = ((chr_h>>0)&2)|((chr_l>>1)&1); lpScn[3] = ((chr_h>>3)&2)|((chr_l>>4)&1); lpScn[7] = ((chr_h<<1)&2)|((chr_l>>0)&1); // Next line lpScn+=128; } // Next pattern lpPtn+=16; } } }
With the above foundation, it is easy to read this code.
The Code contains three for loops. The number of cycles for each for loop took me a long time to understand. There are two pattern tables in NES, 1st for the background pattern table and 1st for the sprite pattern table. The author of the Code further divided a pattern table into four parts. Why? I don't know why, at least I haven't mentioned in my documents that I have divided one pattern table into four copies. In a word, 4*2, 8 will come out.
What does Row 3 mean? I still don't understand. When I debug the program, this judgment is basically true.
5th rows read Part 1 of the pattern table
The cycle of the 6th rows is more explicit here. One pattern table has 256 tile, and the 1/4 pattern table is 64 tile. We can see that the cycle is in tile. You can try to change 64 to 1, recompile the program, and run it. Then you can see what a tile is like.
The number of loops in the first row is 9th. The number of loops in the second row is 1 pixel. With the above explanation of the pattern table, we can understand how to find the specific one pixel.
One thing I want to talk about is that the sorting of 12-19 lines of code statements by the original author of the Code has really interfered with me for a long time. I have always thought that this sorting has specific meanings, if it is written as follows, the rule is obvious.
lpScn[0] = ((chr_h>>6)&2)|((chr_l>>7)&1);lpScn[1] = ((chr_h>>5)&2)|((chr_l>>6)&1);lpScn[2] = ((chr_h>>4)&2)|((chr_l>>5)&1);lpScn[3] = ((chr_h>>3)&2)|((chr_l>>4)&1); lpScn[4] = ((chr_h>>2)&2)|((chr_l>>3)&1);lpScn[5] = ((chr_h>>1)&2)|((chr_l>>2)&1);lpScn[6] = ((chr_h>>0)&2)|((chr_l>>1)&1);lpScn[7] = ((chr_h<<1)&2)|((chr_l>>0)&1);
The meaning of Row 3 is explained. Tile is square. After drawing a row of 8 pixels, you must add the lpScn Image Width 128 to the next row.
Rows 24th are relatively simple. The size of a tile in the pattern table is 16 bytes. to read the next tile, add 16 to the pattern table pointer lpPtn.
The code of the Image Viewer module is similar. The code of the scroll viewer is described in the next section. Scroll is the background. It's a hit. I learned the pattern table, naming table, and attribute table to see how the background is obtained based on these three tables.
This article from the "Three Take Tiger" blog, please be sure to keep this source http://darhx.blog.51cto.com/7920146/1300053