Difference between revisions of "NGT31"
(Add basic NGT31 page) |
(Add more code comments to the 3D section) |
||
(5 intermediate revisions by the same user not shown) | |||
Line 2: | Line 2: | ||
This device provides many useful graphics functions, including accelerated sprite drawing, text, and even drawing lines in 3D. |
This device provides many useful graphics functions, including accelerated sprite drawing, text, and even drawing lines in 3D. |
||
+ | |||
+ | Firmware: https://git.nuclaer-servers.com/Nuclaer/ngt_firmware/-/tree/master |
||
+ | |||
+ | Arduino Library: https://git.nuclaer-servers.com/Nuclaer/nmt_gfx/-/tree/master |
||
== Example Code == |
== Example Code == |
||
Line 177: | Line 181: | ||
</pre> |
</pre> |
||
+ | === 3D Example and Explanation === |
||
Drawing 3D lines, as part of a very simple 3D videogame demonstration: |
Drawing 3D lines, as part of a very simple 3D videogame demonstration: |
||
Line 187: | Line 192: | ||
PS2Keyboard keyboard; |
PS2Keyboard keyboard; |
||
+ | |||
+ | // This buffer is used to build the world command data before sending it to VRAM |
||
+ | uint16_t buffer[256]; |
||
void setup() { |
void setup() { |
||
+ | Serial.begin(115200); |
||
keyboard.begin(4, 2); |
keyboard.begin(4, 2); |
||
ngt.begin(11,10); |
ngt.begin(11,10); |
||
ngt.println("Loading world..."); |
ngt.println("Loading world..."); |
||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
+ | // This object functions as a tool to build the command data, and later send it to the NGT31. |
||
⚫ | |||
+ | Object3D world(ngt, buffer); |
||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
+ | |||
⚫ | |||
⚫ | |||
+ | |||
⚫ | |||
+ | world.addLine(1000,-100,1000,1000,100,1000,2); |
||
− | + | world.addLine(-1000,100,-1000,1000,100,-1000,2); |
|
− | + | world.addLine(-1000,100,-1000,-1000,100,1000,2); |
|
− | + | world.addLine(10,10,10,30,10,10,3); |
|
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
− | + | world.addLine(10,10,30,30,10,30,3); |
|
− | + | world.addLine(30,10,10,30,10,30,3); |
|
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
− | + | world.addLine(10,10,10,10,30,10,3); |
|
− | + | world.addLine(30,10,30,30,30,30,3); |
|
− | + | world.addLine(10,30,10,30,30,10,3); |
|
− | + | world.addLine(10,30,10,10,30,30,3); |
|
+ | // Flush to VRAM. After this the buffer is no longer needed, and can be |
||
⚫ | |||
+ | // used for something else or deallocated. |
||
− | ngt.add_line(10,30,10,10,30,30,3); |
||
+ | world.upload(); |
||
ngt.println("Setting up colors..."); |
ngt.println("Setting up colors..."); |
||
Line 254: | Line 270: | ||
z-=sin(rot*PI/180); |
z-=sin(rot*PI/180); |
||
} |
} |
||
− | ngt.rotatef(rot,0); |
+ | ngt.rotatef(-rot,0); |
ngt.translatef((int32_t)x,0,(int32_t)z); |
ngt.translatef((int32_t)x,0,(int32_t)z); |
||
} |
} |
||
+ | |||
⚫ | |||
+ | // Clear the display of the old world image |
||
⚫ | |||
+ | |||
+ | // Draw the world in one call, based on the data loaded into VRAM |
||
+ | world.draw(); |
||
+ | |||
+ | // Set maximum framerate to 60FPS |
||
delay(max(1,(16+time)-(int32_t)millis())); |
delay(max(1,(16+time)-(int32_t)millis())); |
||
} |
} |
||
Line 264: | Line 287: | ||
void loop() { |
void loop() { |
||
} |
} |
||
+ | </pre> |
||
+ | |||
+ | Here, an <code>Object3D</code> is used to construct a bytecode of the 3D world. This bytecode can be rendered significantly faster than if individual commands were issued for each line or shape in the world. |
||
+ | Due to the speed limitations of the serial port, issuing this many commands per frame would cut the FPS to less than 25 - and that does not include draw time. By using <code>Object3D::draw()</code>, the |
||
+ | serial communication overhead is limited to less than 0.5ms for each draw. |
||
+ | |||
+ | The same Object3D may also be drawn multiple times at different locations by calling <code>draw()</code> multiple times, and calling <code>translatef()</code> in-between to change the position each time. Multiple |
||
+ | Object3Ds may be loaded as well, and drawn as needed. This way each object can be kept in VRAM, and quickly drawn each time it needs to be. |
||
+ | |||
+ | = History = |
||
+ | |||
+ | The below text is based on / copied from an old store listing, written years ago, which details the evolution of this line of graphics devices. |
||
+ | |||
+ | <pre> |
||
+ | ###TVout |
||
+ | Good ole TVout. Get two resistors, a breadboard, and an NTSC output port and you had monochrome video. Looking back, this sucked. I mean, TVout is great, but for me the circuits were unreliable, hard to transport, and delicate. If I knew about the propeller chip at that point at least I would have had colors. I made some games but it wasn’t what I really wanted in the end. |
||
+ | |||
+ | ###Keyboard cable stripping |
||
+ | I didn’t have PS2 ports to plug keyboards into, so I cut the end off the cable and connected the wires to the arduino. That was a crude design. Worked, but also ruined the keyboard. |
||
+ | |||
+ | ###First propeller days |
||
+ | The propeller was nice because it gave me color NTSC (and later VGA). I also built the propeller into permanent circuits capable of NTSC output, which evolved into a standard for my graphics technology. This even powered graphics for my early FPGA designs. |
||
+ | |||
+ | ###NGT20 |
||
+ | Finally: a single, solid, reliable device for video output. Bulky, but had almost all the bells and whistles. No SMD yet, no keyboard yet, but it was the first clean design. It was also the first design with VGA output. I still used the keyboards with stripped cables at this point. |
||
+ | |||
+ | ###NGT30 |
||
+ | All the bells and whistles. Like the NGT20, it is solid and reliable, but it isn’t bulky and also features 3D graphics and a keyboard port. One shield, one keyboard, one arduino, and one monitor. Nothing else is needed. No hanging resistors. No loose wires. No more stripped cables. No more crude designs. |
||
</pre> |
</pre> |
Latest revision as of 00:29, 22 June 2024
The NGT31 is a Parallax Propeller based VGA driver and graphics device, provided as an Arduino shield. It uses a serial connection to the host device for communication, up to 250,000 baud with current firmware.
This device provides many useful graphics functions, including accelerated sprite drawing, text, and even drawing lines in 3D.
Firmware: https://git.nuclaer-servers.com/Nuclaer/ngt_firmware/-/tree/master
Arduino Library: https://git.nuclaer-servers.com/Nuclaer/nmt_gfx/-/tree/master
Example Code
Here is the demo included in the NMT_GFX library (https://git.nuclaer-servers.com/Nuclaer/nmt_gfx/-/blob/master/Examples/NGT31/NGT31_demo/NGT31_demo.ino):
#include <NMT_GFX.h> byte tree[] = { 1,8,0,0, 0b01010100,0b00010101, 0b01010101,0b01010101, 0b01010101,0b01010101, 0b01010101,0b01010101, 0b10100011,0b00001010, 0b10100000,0b00001010, 0b10100000,0b00001010, 0b10101000,0b00101010 }; unsigned short cat[] = { 0, 0, 0xaaa,0x0000, 0xffff, 0xffff, 0xc000, 0xaaa,0xaaa3, 0xf555, 0x5557, 0xf000, 0xaaa,0xaaa3, 0xd555, 0x5555, 0xf000, 0xaaa,0xaaa3, 0x5555, 0x5555, 0x7000, 0xaaa,0xaaa3, 0x5555, 0x4155, 0x7000, 0xaaa,0xaaa3, 0x5555, 0x3c55, 0x70f0, 0xaaa,0x00a3, 0x5555, 0x3f15, 0x73f0, 0xaaa,0x3c23, 0x5555, 0x3fc0, 0x0ff0, 0xaaa,0x0f03, 0x5555, 0x3fff, 0xfff0, 0xaaa,0x83c3, 0x5554, 0xffff, 0xfffc, 0xaaa,0xa0f3, 0x5554, 0xfe3f, 0xfe3c, 0xaaa,0xa803, 0x5554, 0xfc3f, 0xcc3c, 0xaaa,0xaa83, 0x5554, 0xd7ff, 0xffd4, 0xaaa,0xaaa3, 0xd554, 0xd73f, 0x3cd4, 0xaaa,0xaaa3, 0xf555, 0x3f00, 0x00f0, 0xaaa,0x0000, 0xffff, 0xcfff, 0xffc0, 0xaaa,0x003f, 0x0000, 0x0000, 0x0000, 0xaaa,0x003c, 0x03c0, 0x00f0, 0x3c00 }; NMT_GFX ngt; void setup() { Serial.begin(115200); ngt.begin(11,10); ngt.print("Hello!\nThis is an arduino writing\non screen with a "); ngt.println(ngt.get_card_ver()); delay(1200); ngt.println("Look, I can even do colors!"); ngt.set_color(3); ngt.print("green "); ngt.set_color(2); ngt.print("red "); ngt.set_color(1); ngt.println("and white"); ngt.println("(plus much more)"); delay(1400); ngt.fill(2); ngt.println("Screen fills can be done very fast."); delay(1700); ngt.clear(); ngt.println("I dont only do text though, so"); ngt.println("let me load my graphics"); // This line simply sets the size and center of the sprite. // It modifies the 'cat' byte data, so that when it is // uploaded, it will be the correct size, and centered. // We don't do this for the tree, as this data is specified directly // in the initialization of the 'tree' array. Either way // of initializing it works perfectly fine. Sprite catSprite((byte*)cat, 33, 17, 16, 16); int treeHandle = ngt.uploadSprite(tree); int catHandle = ngt.uploadSprite((byte*)cat); delay(750); ngt.println("I can do lines and sprites efficiently too."); byte x_tiles=ngt.x_tiles(); // Get tiles for x and y byte y_tiles=ngt.y_tiles(); ngt.block_color(64+63,0b001100); ngt.block_color(128+63,0b011000); // define tree colors ngt.block_color(192+63,0b010001); for(byte i=0;i<50;i++){ // make all tiles use text coloring ngt.tile_color(i,0); } for(byte i=x_tiles-1;i<50;i+=x_tiles){ // Make bottom tiles use tree coloring ngt.tile_color(i,63); } ngt.sprite(4, 180, 0, treeHandle); ngt.sprite(13, 180, 0, treeHandle); ngt.sprite(48, 180, 2, treeHandle); ngt.sprite(68, 180, 0, catHandle); ngt.sprite(28, 180, 1, treeHandle); ngt.line(0, 150, 30, 130); ngt.fast(60, 150); ngt.fast(90, 130); ngt.fast(120, 150); ngt.fast(150, 130); delay(1000); ngt.println(' '); ngt.set_color(1); ngt.println("The trees are drawn as sprites: an image of one is stored and copied to the screen using only one command, instead of resending the entire tree over serial."); delay(6000); ngt.set_cursor_pos(10, 190); ngt.println("Now I'll show you a moving rainbow sprite."); delay(1750); ngt.clear(); // This sets up the colors for each tile for(byte i=0;i<64;i++){ ngt.block_color(128+i,i); // RAINBOW ngt.block_color(64+i,25); // PINK for Nyan Cat ngt.block_color(192+i,0b101010); // GREY for Nyan Cat } // We only need to call these once. Instead of clearing the screen each frame, // we will erase the old sprite right before drawing a new one. This allows faster // rendering, and frees us from the need to write a message over and over again. ngt.clear(); ngt.set_color(3); ngt.println("Moving rainbow sprite!"); ngt.set_color(0); // This tells the NGT31 to draw only when nothing is being displayed. // Doing this reduces visual artifacts and makes the video data look cleaner. // This only really affects moving objects and changing images: static // text is not affected. // 'B' stands for 'Blanking' - it only draws when no video data is being sent to the monitor. ngt.set_draw_mode('B'); int lastX = 0; int lastY = 40; int vx = 1, vy = 1; while(true) { long ls = millis(); int x = lastX + vx; int y = lastY + vy; ngt.moveSprite(lastX, lastY, x, y, 0, catHandle); lastX = x; lastY = y; if (x >= 300) vx = -1; else if (x <= 0) vx = 1; if (y >= 240) vy = -1; else if (y <= 30) vy = 1; while(ls + 16 > millis()); } } void loop() { }
3D Example and Explanation
Drawing 3D lines, as part of a very simple 3D videogame demonstration:
#include <NMT_GFX.h> #include <PS2Keyboard.h> NMT_GFX ngt; PS2Keyboard keyboard; // This buffer is used to build the world command data before sending it to VRAM uint16_t buffer[256]; void setup() { Serial.begin(115200); keyboard.begin(4, 2); ngt.begin(11,10); ngt.println("Loading world..."); // This object functions as a tool to build the command data, and later send it to the NGT31. Object3D world(ngt, buffer); world.addLine(-1000,-100,-1000,1000,-100,-1000,2); world.addLine(1000,-100,-1000,1000,100,-1000,2); world.addLine(1000,100,-1000,1000,100,1000,2); world.addLine(1000,100,1000,-1000,100,1000,2); world.addLine(-1000,100,1000,-1000,-100,1000,2); world.addLine(-1000,-100,1000,-1000,-100,-1000,2); world.addLine(-1000,-100,1000,1000,-100,1000,2); world.addLine(1000,-100,-1000,1000,-100,1000,2); world.addLine(-1000,-100,-1000,-1000,100,-1000,2); world.addLine(1000,-100,1000,1000,100,1000,2); world.addLine(-1000,100,-1000,1000,100,-1000,2); world.addLine(-1000,100,-1000,-1000,100,1000,2); world.addLine(10,10,10,30,10,10,3); world.addLine(30,10,10,30,30,10,3); world.addLine(30,30,10,30,30,30,3); world.addLine(30,30,30,10,30,30,3); world.addLine(10,30,30,10,10,30,3); world.addLine(10,10,30,10,10,10,3); world.addLine(10,10,30,30,10,30,3); world.addLine(30,10,10,30,10,30,3); world.addLine(10,10,10,10,30,10,3); world.addLine(30,10,30,30,30,30,3); world.addLine(10,30,10,30,30,10,3); world.addLine(10,30,10,10,30,30,3); // Flush to VRAM. After this the buffer is no longer needed, and can be // used for something else or deallocated. world.upload(); ngt.println("Setting up colors..."); for(byte i=0;i<64;i++) { ngt.block_color(128 + i, 0b001011); ngt.block_color(192 + i, 0b111111); } // Supported on newer firmware - reduces flicker ngt.set_draw_mode('B'); ngt.println("Ready to play..."); delay(500); double x=0,z=0; int rot=0; while(true) { int32_t time=millis(); if (keyboard.available()) { // read the next key char c = keyboard.read(); if (c == PS2_LEFTARROW) { rot+=3; } else if (c == PS2_RIGHTARROW) { rot-=3; } else if (c == PS2_UPARROW) { x-=cos(rot*PI/180); z+=sin(rot*PI/180); } else if (c == PS2_DOWNARROW) { x+=cos(rot*PI/180); z-=sin(rot*PI/180); } ngt.rotatef(-rot,0); ngt.translatef((int32_t)x,0,(int32_t)z); } // Clear the display of the old world image ngt.clear(); // Draw the world in one call, based on the data loaded into VRAM world.draw(); // Set maximum framerate to 60FPS delay(max(1,(16+time)-(int32_t)millis())); } } void loop() { }
Here, an Object3D
is used to construct a bytecode of the 3D world. This bytecode can be rendered significantly faster than if individual commands were issued for each line or shape in the world.
Due to the speed limitations of the serial port, issuing this many commands per frame would cut the FPS to less than 25 - and that does not include draw time. By using Object3D::draw()
, the
serial communication overhead is limited to less than 0.5ms for each draw.
The same Object3D may also be drawn multiple times at different locations by calling draw()
multiple times, and calling translatef()
in-between to change the position each time. Multiple
Object3Ds may be loaded as well, and drawn as needed. This way each object can be kept in VRAM, and quickly drawn each time it needs to be.
History
The below text is based on / copied from an old store listing, written years ago, which details the evolution of this line of graphics devices.
###TVout Good ole TVout. Get two resistors, a breadboard, and an NTSC output port and you had monochrome video. Looking back, this sucked. I mean, TVout is great, but for me the circuits were unreliable, hard to transport, and delicate. If I knew about the propeller chip at that point at least I would have had colors. I made some games but it wasn’t what I really wanted in the end. ###Keyboard cable stripping I didn’t have PS2 ports to plug keyboards into, so I cut the end off the cable and connected the wires to the arduino. That was a crude design. Worked, but also ruined the keyboard. ###First propeller days The propeller was nice because it gave me color NTSC (and later VGA). I also built the propeller into permanent circuits capable of NTSC output, which evolved into a standard for my graphics technology. This even powered graphics for my early FPGA designs. ###NGT20 Finally: a single, solid, reliable device for video output. Bulky, but had almost all the bells and whistles. No SMD yet, no keyboard yet, but it was the first clean design. It was also the first design with VGA output. I still used the keyboards with stripped cables at this point. ###NGT30 All the bells and whistles. Like the NGT20, it is solid and reliable, but it isn’t bulky and also features 3D graphics and a keyboard port. One shield, one keyboard, one arduino, and one monitor. Nothing else is needed. No hanging resistors. No loose wires. No more stripped cables. No more crude designs.