Jump to content
IGNORED

First person shooter on the TI? :)


Bones-69

Recommended Posts

The 9938 has a move command where you specify a rectangle on the screen and where to move it. This is quite fast, as it happens all inside the 9938.

 

See the V9938 manual, section 4.3 (high speed move VRAM to VRAM).

Link to comment
Share on other sites

Yea drawing for games on the 9938 and 9958 are fast and require very little code once set up is done.

A program called XHI for the 9938/9958 had a demo that was very impressive and showed what could be done.

A program called Y.A.P.P. made TI Artist look pretty primitive.

Funnel Web had a 80 Column version that rivaled most PC word processors in function and form.

 

http://en.wikipedia.org/wiki/Yamaha_V9938

 

http://en.wikipedia.org/wiki/Yamaha_V9958

 

Built in Mouse routine (9938) so would make a much better interface using a mouse.

Hardware Accelerated commands built into the 9958 but removed the Mouse/Light Pen commands.

 

Yea to bad these are not used they would really make the SAMS more useful to use.

 

Rich

Link to comment
Share on other sites

The 9938 has a move command where you specify a rectangle on the screen and where to move it. This is quite fast, as it happens all inside the 9938.

 

See the V9938 manual, section 4.3 (high speed move VRAM to VRAM).

Hi Michael. I have used the 9938 commands in the past - they are quick but not nearly fast enough for any smooth scrolling of the entire screen.

Link to comment
Share on other sites

Tim, please don't spoil my dreams ... :-) I always hoped for a bright 9938 future with games with sophisticated graphics, at least at C64 level. (SCNR)

 

Some ideas:

 

1. Full screen scrolling should rarely be needed, maybe limited to some rectangular area.

2. Maybe I should really try to benchmark the commands to have some numbers. I already mentioned my benchmark tool which takes the time from the RTC on the Geneve.

3. I already suspected that some commands may be too slow to be suitable for all cases. In my Fractals program I used the fill command to wipe the screen, and I had to split it in two actions (because you cannot fill the whole screen in one go). I remember that I could see when the first and second wipe happened, and everything you can see is too slow, i.e. it happens in times of more than 100 ms.

Link to comment
Share on other sites

Tim, please don't spoil my dreams ... :-) I always hoped for a bright 9938 future with games with sophisticated graphics, at least at C64 level. (SCNR)

 

Some ideas:

 

1. Full screen scrolling should rarely be needed, maybe limited to some rectangular area.

2. Maybe I should really try to benchmark the commands to have some numbers. I already mentioned my benchmark tool which takes the time from the RTC on the Geneve.

3. I already suspected that some commands may be too slow to be suitable for all cases. In my Fractals program I used the fill command to wipe the screen, and I had to split it in two actions (because you cannot fill the whole screen in one go). I remember that I could see when the first and second wipe happened, and everything you can see is too slow, i.e. it happens in times of more than 100 ms.

 

On the 9938 or 9958 your just change the screen address and it changes the entire screen instantly.

Now for SCROLLING in the XB program XHI written in Assembly for use from XB is has a Scroll routine that uses this method and you get no flicker and the screen scrolls FAST!

It also has a draw function like Turtle that is fast and very good for use with sprites.

Link to comment
Share on other sites

Tim, please don't spoil my dreams ... :-) I always hoped for a bright 9938 future with games with sophisticated graphics, at least at C64 level. (SCNR)

 

Some ideas:

 

1. Full screen scrolling should rarely be needed, maybe limited to some rectangular area.

2. Maybe I should really try to benchmark the commands to have some numbers. I already mentioned my benchmark tool which takes the time from the RTC on the Geneve.

3. I already suspected that some commands may be too slow to be suitable for all cases. In my Fractals program I used the fill command to wipe the screen, and I had to split it in two actions (because you cannot fill the whole screen in one go). I remember that I could see when the first and second wipe happened, and everything you can see is too slow, i.e. it happens in times of more than 100 ms.

Heheheh Sorry :)

 

I should clarify something. I am talking about scrolling mode 6 or mode 7, which is close to 64K of bitmap data. This is why I have been trying to figure out how the horizontal offset register can be used for scrolling purposes. The datasheet has not enlightened me as I have been unable to figure out how one could use the register to their advantage.

 

I suppose I need to encounter a few more VDP/game battles to raise my experience and manna levels ;)

Link to comment
Share on other sites

  • 3 weeks later...

I'm still working on the code for a ray-tracer, but things are progressing slowly due to lack of free time. I now have a floating-point free algorithm up and running, but I need to fix some boundary/overflow issues, divides by negative numbers. etc... before it'll render faithfully. It does seem slow though, so I might need to move some of the calculations to asm routines (remember, I'm using gcc), but my gut tells me it should be doable on the TI. If only I had more spare time :).

Link to comment
Share on other sites

  • 4 weeks later...

I hope I'm not being politically incorrect by making the following non-purist suggestion, but WHY limit the project to backward compatibility with TI hardware? One person already mentioned the Nano-PEB already as a speed-access solution. Does the F18A video not have a little more 'punch' that could be utilized?

 

I'd be more than happy to shell out some money for a 'Wolfenstein-Like' game.

 

The only problem I see is the severe lack of F18A's. If a game like this became available, you can bet there would be a growing demand for the video upgrade kit.

Link to comment
Share on other sites

The F18A does have more punch and could certainly help in a game like this. The entire rendering system could be done with the GPU which is estimated to be about as fast as a 68000 CPU. I started working on a wolfenstien style raycaster demo, but I just don't have the time right now to get it running. Seems to be a common theme lately.

 

The only problem I see is the severe lack of F18A's.

 

Just to be clear on this, the lack of F18As is not due to them being unavailable. I try to keep 4 in stock all the time and build more as necessary. I am out right now, but I have 4 on the bench to be tested and the stock will be updated soon. To date there are about 100 F18A boards out in the wild.

 

  • Like 2
Link to comment
Share on other sites

  • 3 months later...

I've started working on this again, but to make things easier decided to make a PC version first, as this my first ray caster, and introduce all of the TI's limitations to that code first before porting it over to the TI.

 

This screenshots shows how it renders using the limitations of the TI (TI color palette, 64x64 pseudo-resolution using color table manipulations only, integer math only, etc...). *edit* but just to clarify, this is not a real TI screenshot yet!

post-33891-0-23276800-1379624931_thumb.png

 

It looks better when moving around, but to me it looks very serviceable. On the F18A I'll add textures and a custom color palette, hopefully render the slices using the GPU, but for a stock machine this is doable (potential speed issues notwithstanding). I'm currently trying to port it to the TI and the code compiles, but running it just gives all kinds of weird results (graphics memory corruption, weird beeps from the sound chip, hanging after a couple of frames, ...). I'm thinking I run into memory location issues or there might be a small bug in the GCC generated code (jumping to unitialized memory maybe?) but having never really programmed the TI in assembly, I have not enough knowledge of the memory map you need to take into account for a cartridge game. I know text segments (code) are supposed to be at the 8k block starting >6000 and data segments in the lower 8k (>2000), which I think I'm doing, but maybe there are other limitations that I'm not aware of?

 

Basically, I believe the C code is up to snuff and this should almost compile out of the box but I am still doing something wrong and I need to figure out what. Any input as to how to debug this type of stuff greatly appreciated, I'll keep on trucking but wanted to put something out there to keep the pressure on and motivation going :).

Edited by TheMole
  • Like 2
Link to comment
Share on other sites

I'd suggest using the EA#5 builds out of the compiler to start - you have 24k+8k to work with instead of 8k+8k, makes it a little easier to get around.

 

There are still some bugs in the compiler, so you will have to start stepping through the code to see where it goes south, and work out if it's a compiler or programmer error. Thankfully the issues are few, but they seem to be largely centered on conversion between int and char - changing everything to work with int can help you determine if that's the case.

 

As a couple of debug suggestions: make the compiler keep temporary files (--keep-temp, IIRC) so that you have the .s files to compare against the .c files. Then you can look at the various functions, which makes it easier to keep track while single-stepping, too. It's easier to build it bit by bit, so you can narrow down the function(s) that is not working, but a binary search will get you there pretty quickly. If you can't do that, you can at least set breakpoints on each of the entry points (using the symbol table in the map), and just check them off as you hit them till you find the one it crashes on.

 

That said, your assumption of the memory map is correct, as long as you have 32k attached (I assume you are testing on an emulator, which certainly should). I can try to help you troubleshoot if you want to post the code (so I can generate a listing file) and output.

Link to comment
Share on other sites

I can try to help you troubleshoot if you want to post the code (so I can generate a listing file) and output.

I would certainly welcome a second pair of eyes. I've attached the full source to this post, as well as the cart image. It compiles against your libti99 for VDP functions, but I haven't included the headers or library. You will need to change a path or two in the makefile in order to build it yourself, but it should be fairly straightforward. I used a relative but hardcoded path for the library's h files in the code itself (all relevant logic is in wolfie3.c), so you'll also need to change that to wherever you keep your headers. At some point I'll move all file paths to the makefile but that's a concern for later.

 

Note that the "pixel plot" function might be horribly broken, I added it quickly but haven't been able to get so far as to debug that just yet. Let me know if anything is unclear, I really appreciate the offer for help.

 

wolfie3.c looks like this:

/*************************/
/* TI-99/4A Ray caster   */
/* 2013 - Danny Lousberg */
/*************************/

// Includes
#include "../libti99-master/vdp.h"  // Tursi's libti99, VDP functions
#include "../libti99-master/halt.h"
#include "lookup.h"					// sin/cos/etc... lookup tables
#include "tistdio.h"				// Quick set of functions for keyboard scanning

// Defines
#define		MAX_INT 			32768
#define 	MAP_WIDTH 			24
#define 	MAP_HEIGHT 			24
#define 	CELL_SIZE 			32	// Size of each wall "cube" on the map: width, depth and height
#define		SCREEN_WIDTH		64	// Number of rays to cast, or number of columns, or slices
#define		SCREEN_HEIGHT		64	// Height of the rendering surface, in pseudo-pixels; not including bottom area
#define		FOV					64	// Field Of View, aka how wide the arc of rays needs to be
#define 	SCREEN_DISTANCE 	22	// Distance to screen projection plane
									// Formula for this is (SCREEN_WIDTH / 2) / tan(FOV / 2)
#define		ANGLE_STEP			1	// Should be FOV / SCREEN_WIDTH, so FOV needs 
									// to be a multiple of SCREEN_WIDTH, effectively 
									// giving a max resolution of 64 for 
									// realistic looking rendering
#define		SCREEN_BUFFER_SIZE	2048

// Global Variables
char worldmap[MAP_WIDTH][MAP_HEIGHT] =
{
	{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,2,2,2,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1},
	{1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,3,0,0,0,3,0,0,0,1},
	{1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,2,2,0,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,4,0,0,0,0,5,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,4,0,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};

// RAM buffer for first two color tables, or our 64x64x4bit pseudo-pixel buffer
unsigned char screenbuffer[SCREEN_BUFFER_SIZE];

// GCC-BUG?: Where this is used, a normal division would throw
// GCC-BUG?: the following "internal compiler error":
// GCC-BUG?: compiler error: in subreg_highpart_offset, at emit-rtl.c:1304
inline int asm_div(int dividend, int divider)
{
	int quotient = 0;
	
	asm
	(
		"LI		r0,0	\n\t"
		"MOV	%2,r1	\n\t"
		"MOV	%1,r2	\n\t"
		"DIV	r2,r0	\n\t"
		"MOV	r0,%0	\n\t"
		:"=r"(quotient)
		:"r"(divider),"r"(dividend)
		:"r0","r1","r2"
	);

	return quotient;
}

// "Plots" a pixel defined by linear coordinates onto the color table
#define pattern_size 	8
#define patterns_width	32
#define patterns_height	16
inline void plot_pixel_bitmap(int x, int y, int color)
{
	int pattern_location_x, pattern_location_y, membase_pattern;
	
	pattern_location_x = x / 2;
	pattern_location_y = y / 4;
	membase_pattern = (pattern_location_x * pattern_size) + ((pattern_size * patterns_width) * y);
	
	membase_pattern += (y % 4) * 2;
	if (x % 2)
		screenbuffer[membase_pattern] |= color;
	else
		screenbuffer[membase_pattern] |= (color << 4);
}

// Return absolute value of an int
int abs(int number)
{
	return (number<0)?-number:number;
}

// Render a vertical line, called a "slice" in ray casting terms
void draw_slice(int column, int slicestart, int sliceend, int color)
{
	int y;
	for (y = slicestart; y < sliceend; y++)
		plot_pixel_bitmap(column, y, color);
}

// The actual ray casting algorithm, called once per frame
// Basically, we iterate over each screen column and calculate the angle of view
// that is associated with that column. Then we "cast a ray" from the player's position
// along that angle until we hit a wall in our map.
// We use the distance to that wall to calculate the height of the slice to be 
// rendered for the current column (the further away, the smaller the slice)
void cast_rays(int playerx, int playery, int playerangle)
{
  	int	column;								// column iterator, range: 0-SCREEN_WIDTH
  	int rayangle;	
  	int hordist, verdist;					// distance to the closest wall in horizontal _and_ vertical direction
	int mapx, mapy;							// temp values for checking a hit on the map, will be copied into hitx,hit
	int hitx, hity;							// coords in map space of the _closest_ hit (either vert or hort)
	int distance;							// distance of the _closest_ hit (either vert or hort)
  	int xstep, ystep;						// How many units to step from one wall-boundary to the next
  	int checkx, checky;						// coords of the first intersection, before we start "stepping"
  	int side = 0;

	// First ray, left-most column starts at half the FOV counterclockwise from the player's angle
	rayangle = playerangle - FOV / 2;
	hitx = hity = 0;

    for(column = 0; column < SCREEN_WIDTH; column++)
    {
	  // Normalize angle to 0-359 range
	  if (rayangle < 0)
	  	rayangle += 360;
	  if (rayangle >= 360)
		rayangle -= 360;

	  // Initialize distance, will be used in comparison later on
	  // and there is a code path that could leave this undefined
	  distance = MAX_INT;

	  // Check for horizontal intersections
	  if ((rayangle != 0) && (rayangle != 180))
	  {
	    int hit;						// Boolean to stop checking for walls
	    
	  	// initialize to longest distance as we will be picking the shortest distance
	  	// to a hit between the two axis
	  	hordist = MAX_INT;

		// Calculate first intersection point on next cell along ray
		// First we calculate the Y coord (easiest)
		if (rayangle > 180)
		{
			// ray is facing up in the map; so we put the checkpoint RIGHT ABOVE (hence the - 1)
			// the boundary of the cell we are in on the Y-axis
			checky = ((playery / CELL_SIZE) * CELL_SIZE) - 1;
			checkx = playerx - ( (256 * (playery - checky)) / TAN255[rayangle] );
			xstep = -STEPX[rayangle]; // = (256 * -CELL_SIZE) / TAN255[rayangle];
			ystep = -CELL_SIZE;
		}
		else
		{
			// ray is facing down in the map; so we put the checkpoint right AT
			// the boundary of the cell BELOW the one we are in on the Y-axis
			checky = ((playery / CELL_SIZE) * CELL_SIZE) + CELL_SIZE;
			checkx = playerx - ( (256 * (playery - checky)) / TAN255[rayangle] );
			xstep = STEPX[rayangle]; // = (256 * CELL_SIZE) / TAN255[rayangle];
			ystep = CELL_SIZE;
		}

		hit = 0;
		mapx = checkx / CELL_SIZE;
		mapy = checky / CELL_SIZE;
		while (!hit)
		{
			// Ray has left the map; need to break out of the loop
			if ( (mapx < 0) || (mapy < 0) || (mapx > MAP_WIDTH) || (mapy > MAP_HEIGHT) )
			{
				hordist = MAX_INT;
				break;
			}	
		
			// if we hit a non-zero field in the map, we're on the edge of a wall
			if (worldmap[mapx][mapy])
			{
				hit = 1;
				hordist = abs( 256 * (checky - playery) / SIN255[rayangle] );
			}
			else
			{
				// No wall? Then we take steps to the next boundary and
				// reset the map coords to be checked
				checkx += xstep;
				checky += ystep;
				mapx = checkx / CELL_SIZE;
				mapy = checky / CELL_SIZE;
			}
		}
	 	
		// Only checked horizontal hits yet, so these are by definition our best guess for
		// the closest hit. We'll check these values against the vertical results later on
		// so we store in the variables for the final result for now...
		hitx = mapx;
		hity = mapy;
		distance = hordist;
	  }
	  else
	  {
	  	// Angles 0 and 180 have a cos of zero
	  	// or in other words, we will never see a horizontal intersection
	  	hordist = MAX_INT;
	  }

	  // Check for vertical intersections
	  if ((rayangle != 90) && (rayangle != 270))
	  {
	    int hit;						// Boolean to stop checking for walls
	    
	  	// initialize to longest distance as we will be picking the shortest distance
	  	// to a hit between the two axis
	  	verdist = MAX_INT;

		// Calculate first intersection point on next cell along ray
		// First we calculate the X coord (easiest)
		if ( (rayangle < 270) && (rayangle >= 90) )
		{
			// Going left, check against left boundary of current cell
			checkx = ((playerx / CELL_SIZE) * CELL_SIZE) - 1;
			checky = playery - (( (playerx - checkx) * TAN255[rayangle] ) >> ;
			ystep = -STEPY[rayangle]; // = (-CELL_SIZE * TAN255[rayangle]) / 256;
			xstep = -CELL_SIZE;
		}
		else
		{
			// facing right, check against LEFT boundary of NEXT cell
			checkx = ((playerx / CELL_SIZE) * CELL_SIZE) + CELL_SIZE;
			checky = playery - (( (playerx - checkx) * TAN255[rayangle] ) >> ;
			ystep = STEPY[rayangle]; // = (CELL_SIZE * TAN255[rayangle]) / 256;
			xstep = CELL_SIZE;
		}

		hit = 0;
		mapx = checkx / CELL_SIZE;
		mapy = checky / CELL_SIZE;
		while (!hit)
		{
			// Ray has left the map; need to break out of the loop
			if ( (mapx < 0) || (mapy <0) || (mapx > MAP_WIDTH) || (mapy > MAP_HEIGHT) )
			{
				verdist = MAX_INT;
				break;
			} 

			// if we hit a non-zero field in the map, we're on the edge of a wall
			if (worldmap[mapx][mapy])
			{
				hit = 1;
				verdist = abs( 256 * (checkx - playerx) / COS255[rayangle] );
			}
			else
			{
				// No wall? Then we take steps to the next boundary and
				// reset the map coords to be checked
				checkx += xstep;
				checky += ystep;
				mapx = checkx / CELL_SIZE;
				mapy = checky / CELL_SIZE;
			}
		}
	  }
	  else
	  {
	  	// cos of 90 and 270 is zero, so there is never an intersection with a vertical wall
	  	verdist = MAX_INT;
	  }
  	
	  // See which of our hits we should take into account
	  // Default was horizontal, but we take vertical if that distance is shorter
	  side = 0;
	  if (verdist <= hordist)
	  {
		hitx = mapx;
		hity = mapy;
		distance = verdist;
		side = 1;
	  }
	  
	  // calculate slice height based on distance
	  int sliceheight, slicestart, sliceend;
	  if (distance)
	  	sliceheight = asm_div(SCREEN_DISTANCE * CELL_SIZE, distance);
	  else
	  	sliceheight = SCREEN_HEIGHT;
	  slicestart = (SCREEN_HEIGHT - sliceheight) / 2;
	  sliceend = slicestart + sliceheight;

      // Start of rendering this slice
	  // Color of wall slice
	  char color;
	  if (side != 1)
	  {
		// Front view, light colors
		switch(worldmap[hitx][hity])
		{
			case 1:  color = 8;   break;   // red
			case 2:  color = 2;   break;   // green
			case 3:  color = 5;   break;   // blue
			case 4:  color = 15;  break;   // white
			default: color = 11;  break;   // yellow
		}
	  }
	  else
	  {
		// Side view, dark colors
		switch(worldmap[hitx][hity])
		{
			case 1:  color = 6;   break;   // dark red
			case 2:  color = 12;  break;   // dark green
			case 3:  color = 4;   break;   // dark blue
			case 4:  color = 14;  break;   // gray
			default: color = 10;  break;   // dark yellow
		}
	  }

      // draw the pixels of the stripe as a vertical line
      draw_slice(column, slicestart, sliceend, color);
            
      // Prepare for next column
      rayangle += ANGLE_STEP;
    }
}

void init_patterntable()
{
	// Init all patterns in first two pattern tables to 0xF0
	vdpmemset(gPattern, 0xF0, 768 * 2);
}

void init_nametable()
{
	// Init first two out of three nametable with increasing values
	vdpwriteinc(gImage,   0, 768);
	vdpwriteinc(gImage, 768, 768);
}

int main(int argc, char *argv[])
{
	int x;
	int posx, posy, angle;

	// Init graphics system
	x = set_bitmap(0);
	VDP_SET_REGISTER(VDP_REG_MODE1, x);
	init_patterntable();
	init_nametable();

	// Initialize player position and angle	
	posx = 18 * CELL_SIZE;
	posy = 18 * CELL_SIZE;
	angle = 0;

	while(1)
	{
		// raycasting
		cast_rays(posx, posy, angle);

		// blit screenbuffer
		vdpmemcpy(gColor, screenbuffer, SCREEN_BUFFER_SIZE);
		vdpwaitvint();

		// Scan keys
		scan_keys();

		// RIGHT/'D' pressed, turn right
		if ( check_key(2,0x2000) )
			angle += 2;

		// LEFT/'S' pressed, turn left
		if ( check_key(1,0x2000) )
			angle -= 2;

		// Normalize angle to 0-359 range
		if (angle < 0)
			angle += 360;
		if (angle >= 360)
			angle -= 360;
	}
	halt();

    return 0;
}

wolfie3d.zip

wolfie3dC.bin

Link to comment
Share on other sites

maybe I'm wrong something...

It crashes on Classic99 v369

 

File ini:

 

[usercart2]

; *** Wolfie 3D

name="Wolfie 3D"

rom0=C|6000|2000|mods\wolfie3dC.bin

 

Sorry, I should have been more clear. This is indeed non-working code, posted so Tursi could have a look. I hope to have something for everyone to play with soon enough ;).

Link to comment
Share on other sites

  • 2 months later...

Probably the same technique as the C64: You update the scroll register once each frame, (increment it or decrement it) which moves the screen up/down by one pixel. On the 8th frame, rather you reset the scroll register to 0, and do a 'character level' scroll. If the character scroll is done at the right time, you don't see it, as it's done as the raster is on the flyback.

 

This was very simple on the C64, because the video memory was part of the CPU memory. Not so simple on our box with access via an I/O port. :-(

 

So, probably best to keep the video frame in CPU ram, then write it to VRAM as quick as possible (no real problem). Now, you can be working on your character level scrolling in CPU RAM while doing other stuff (like updating the scroll register, reading joysticks, updating sprites etc). Then, when it's time to reset the scroll register, your next frame has been scrolled in CPU RAM and you just blast it out to VRAM.

On msx2 the vertical scroll is done in a more smart way than on c64, you do not need to reset every 8 frame the register and do a massive update of the entire screen. Instead, the display is a circular buffer, so you only need to supply the line that comes into visible area, that require a very little effort and cpu time (you write 128 bytes) and it is doable with out instruction in a very fast way.

 

you basically continue to write the register from 0 to 255 then wrap around to 0. The screen area start to be displayed to the value on the register and ends 211 lines down. It's circular, it's easy!

 

The downside is that also sprites move. this is by design but you need to take into consideration that if you want a scrolling background and also the sprites that are not moving vertically. this lead to a very annoying issue. you need to update sat sprite table when you move the background

Edited by microprocessor
  • Like 1
Link to comment
Share on other sites

On msx2 the vertical scroll is done in a more smart way than on c64, you do not need to reset every 8 frame the register and do a massive update of the entire screen. Instead, the display is a circular buffer, so you only need to supply the line that comes into visible area, that require a very little effort and cpu time (you write 128 bytes) and it is doable with out instruction in a very fast way.

 

you basically continue to write the register from 0 to 255 then wrap around to 0. The screen area start to be displayed to the value on the register and ends 211 lines down. It's circular, it's easy!

 

The downside is that also sprites move. this is by design but you need to take into consideration that if you want a scrolling background and also the sprites that are not moving vertically. this lead to a very annoying issue. you need to update sat sprite table when you move the background

 

This is excellent confirmation of what I've been playing around with using a 9938 and its scroll register! There is another thread for 9938/9958 discussion where Eck and I have been talking about character generation, so I've been distracted with that. :)

 

How would you recommend sending the 128 bytes and updating the sprite table, i.e., is it better (faster?) to use the VDP high speed commands or just write it out? It seems there are some timing and setup trade-offs to both.

Link to comment
Share on other sites

 

This is excellent confirmation of what I've been playing around with using a 9938 and its scroll register! There is another thread for 9938/9958 discussion where Eck and I have been talking about character generation, so I've been distracted with that. :)

 

How would you recommend sending the 128 bytes and updating the sprite table, i.e., is it better (faster?) to use the VDP high speed commands or just write it out? It seems there are some timing and setup trade-offs to both.

I do not know the exact timings of a 9900 write so i cannot tell you exactly. However, if you are doing data output outside active area i'm pretty sure you can't overload the vdp. in active area, however you can send data to vdp data port with a rate of 1 byte every 4.5 microseconds (worst case). It obviously depend on the situation. the vdp is more or less free depending on:

 

- sprites, if they are turned on or off

- active/inactive area

- screen mode (tms modes require less bandwidth)

 

do not bother to send data with a vdp command in progress. the internal vdp logic, will suspend I/O operations of vdp commands if you send data from cpu.

about using or not vdp commands, depends . Ask yourself and decide:

 

the data i need to put on the scrolled scanline is already in vram and suitable for vdp commands? if yes, use the vdp commands. ohterwise, set vram ptr a la TMS and push the data.

  • Like 1
Link to comment
Share on other sites

I do not know the exact timings of a 9900 write so i cannot tell you exactly. However, if you are doing data output outside active area i'm pretty sure you can't overload the vdp. in active area, however you can send data to vdp data port with a rate of 1 byte every 4.5 microseconds (worst case). It obviously depend on the situation. the vdp is more or less free depending on:

 

- sprites, if they are turned on or off

- active/inactive area

- screen mode (tms modes require less bandwidth)

 

do not bother to send data with a vdp command in progress. the internal vdp logic, will suspend I/O operations of vdp commands if you send data from cpu.

about using or not vdp commands, depends . Ask yourself and decide:

 

the data i need to put on the scrolled scanline is already in vram and suitable for vdp commands? if yes, use the vdp commands. ohterwise, set vram ptr a la TMS and push the data.

This makes perfect sense. I am using 9938 Graphics mode six. Comparison based on some existing code I am modifying:

 

Old method: Use VDP high speed command to copy up to 54,272 bytes per scroll. Write the next line to the screen. Very slow.

 

New method: Process and write the next displayable line to the inactive area. A single pixel row requires 512 pixels x 1 pixel high x 1 byte/2 pixels = 256 bytes. A character-height scroll requires 256 bytes/row * 8 rows = 2048 bytes. When it is time to scroll, adjust register #23. ;)

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...