BS Image Loader Example

Programming examples for the Psy-Q SDK
Post Reply
User avatar
LameGuy64
Verified
Psy-Q Enthusiast
Psy-Q Enthusiast
Posts: 388
Joined: Apr 10, 2013
I am a: Hobbyist Game Developer
Motto: Commercial or not, play it!
PlayStation Model: H2000/7000
Location: Philippines
Contact:

BS Image Loader Example

Post by LameGuy64 » December 11th, 2013, 1:19 pm

Here's an example BS image loader I've put together as it could be useful to some including me. Being that I've wrote the entire loader as a single function for convenience, it should be very easy to implement into your own games/apps... Just make sure you have enough memory available when calling the function.

Basically, BS (or Bit Stream) images are like JPEG images but can be decoded quickly by the MDEC chip inside the PlayStation. Being that BS is similar to JPEG, it is lossy and is not a desirable format for texture data but, because BS images are a lot smaller than an uncompressed 640x480 24-bit TIM image (which is very close to a megabyte), it is great for quickly loading and displaying a hi-res 640x480 illustration in 24-bit color during loading sessions.

Here's the code:

Code: Select all

#include <sys/types.h>
#include <libgte.h>
#include <libgpu.h>
#include <libpress.h>

// C file containing our sample BS image
#include "scarlett.c"

void LoadBS (int width, int height, int x, int y, int mode, u_long* bsfile);

int main () {

	RECT	rect={ 0, 0, 1024, 512 };
	DISPENV disp;
	
	// Reset the GPU and clear the entire framebuffer
	ResetGraph(0);
	ClearImage2(&rect, 0, 0, 0);
	
	// Set 640x480 24-bit color video mode (note: only framebuffer operations work in this mode)
	SetDefDispEnv(&disp, 0, 0, 640, 480);
	disp.isrgb24 = 1;
	PutDispEnv(&disp);
	
	// Remove the display mask
	SetDispMask(1);
	
	// Load the BS image
	LoadBS(640, 480, 0, 0, 1, (u_long*)&bs_scarlett[0]);
	
	printf("Decode complete!\n");

}

void LoadBS (int width, int height, int x, int y, int mode, u_long* bsfile_ptr) {

	/*
	
	*TINY* BS image loader coded by Lameguy64 of Meido-Tek Productions (2013)
	
	SYNTAX:
	width, height 	- Size of the BS image (image must be a multiple of 16 for both dimensions)
	x, y			- Framebuffer location on where to draw the decoded image
	mode			- Decode color depth mode (0 - 16-bit, 1 - 24-bit)
	bsfile_ptr		- Pointer to the BS image data to decode
	
	NOTE:
	The BS image must be at least 24 sectors (49152 bytes) or less in size otherwise, the MDEC
	will freeze at a certain point of decoding. This can be set in MC32 by clicking the Attributes
	button, click the Custom check box, enter 24 sectors for maximum frame size, and then click
	the Variable Frame Size radio button before converting.
	
	*/

	int		mdec_col;
	int		mdec_cellsize	= (16 + (8 * mode));		// Calculate the size of a cell (16 pixels in 16-bit mode, 24 'odd' pixels in 24-bit mode)
	int		mdec_stripsize	= (mdec_cellsize * height); // Calculate the size for storing several cells in a single strip
	RECT	mdec_rect;
	
	u_long 	mdec_strip[mdec_stripsize];					// Buffer for one vertical strip of decoded image data
	u_long	mdec_buff[DecDCTBufSize(bsfile_ptr) + 1]; 	// I want to avoid using mallocs (for convenience :>)
	
	DecDCTReset(0);							// Reset the MDEC chip
	
	DecDCTvlc(bsfile_ptr, &mdec_buff[0]);	// Decompress DCT data
	DecDCTin(&mdec_buff[0], mode);			// Set MDEC input to point to decompressed DCT data
	
	// Parameters for uploading the strip of decoded MDEC cells into the framebuffer
	mdec_rect.y = y;
	mdec_rect.w = mdec_cellsize;
	mdec_rect.h = height;
	
	// This is where the actual decode process takes place
	for (mdec_col = 0; mdec_col < (width / 16); mdec_col += 1) {
	
		DecDCTout(&mdec_strip[0], mdec_stripsize / 2);	// Grab a strip of decoded image data
		DecDCToutSync(0);								// Wait for the decode to finish
		
		// Upload the strip into the framebuffer
		mdec_rect.x = x + (mdec_cellsize * mdec_col);
		LoadImage(&mdec_rect, mdec_strip);
	
		// Wait until the upload has finished before loading another strip
		DrawSync(0);
	
	}

}
Compile it by entering the following into the command prompt (assuming you've already run PSPATHS.BAT):

Code: Select all

ccpsx -O -Xo$80010000 bs_test.c -obs_test.cpe,bs_test.sym
cpe2x bs_test.cpe
Here's a little tutorial on how to encode BS images:

1. Convert the image you're going to use into a TIM image using TIMTOOL (but make sure you import in 24-bit color depth).
2. Open up MC32 and select 'bs' on the right drop-down list on the small window.
3. Click the Attributes button and a window should pop-up.
4. Check the Custom box and enter 24 sectors as the maximum frame size (you can use a lower value for a smaller file size but lower quality)
5. Click the 'Variable Frame Size' option and then close the window.
6. Select the TIM file you've created with TIMTOOL on the left text box on the small window.
7. Once selected, a preview of the image should show up. Click the Encode button to see what it'll look like when encoded to BS.
8. Click the Go button to begin conversion.
You do not have the required permissions to view the files attached to this post.
Please don't forget to include my name if you share my work around. Credit where it is due.

Dev. Console: SCPH-7000 with SCPH-7501 ROM, MM3, PAL color fix, Direct AV ports, DB-9 port for Serial I/O, and a Xplorer FX with Caetla 0.35.

DTL-H2000 PC: Dell Optiplex GX110, Windows 98SE & Windows XP, Pentium III 933MHz, 384MB SDRAM, ATI Radeon 7000 VE 64MB, Soundblaster Audigy, 40GB Seagate HDD, Hitachi Lite-on CD-RW Drive, ZIP 250 and 3.5" Floppy.

User avatar
NITROYUASH
Verified
Serious PSXDEV User
Serious PSXDEV User
Posts: 124
Joined: Jan 07, 2018
I am a: Game Designer
PlayStation Model: SCPH-5502
Location: Russian Federation
Contact:

Post by NITROYUASH » March 19th, 2019, 11:31 pm

This code is one of the first things that i tested. Thanks!
But i have a maybe dumb question. Can we use LoadBS() and sort TIM's and/or work with frame buffer at the same time?

Here is the example from "Duke Nukem: Time to Kill":
► Show Spoiler
It's a .BS image, but it's using some TIM's (font and button icon) at the same time.

User avatar
TriMesh
Verified
PSX Aptitude
PSX Aptitude
Posts: 225
Joined: Dec 20, 2013
PlayStation Model: DTL-H1202
Location: Hong Kong

Post by TriMesh » March 20th, 2019, 4:13 am

NITROYUASH wrote: March 19th, 2019, 11:31 pm This code is one of the first things that i tested. Thanks!
But i have a maybe dumb question. Can we use LoadBS() and sort TIM's and/or work with frame buffer at the same time?

Here is the example from "Duke Nukem: Time to Kill":
► Show Spoiler
It's a .BS image, but it's using some TIM's (font and button icon) at the same time.
You can certainly combine them on the same screen - just wait until the background image is loaded and then copy the other stuff to the framebuffer using LoadImage() - note that since the output of the MDEC is 24 bit, anything you write on top of it has to be 24 bit too. One other thing to watch out for is that LoadImage() writes a rectangular area and there is no alpha channel - one possible solution to this is to grab the background using StoreImage(), then update it with the non-transparent pixels and then write it out again.

User avatar
LameGuy64
Verified
Psy-Q Enthusiast
Psy-Q Enthusiast
Posts: 388
Joined: Apr 10, 2013
I am a: Hobbyist Game Developer
Motto: Commercial or not, play it!
PlayStation Model: H2000/7000
Location: Philippines
Contact:

Post by LameGuy64 » March 20th, 2019, 11:23 am

You can make the MDEC output 16-bit pixels instead of 24-bit so you can use the output as a texture since the GPU only supports drawing pixels in 16-bit color and reading textures in 16-bit color depth or less. This is how that video screen in one of the Tony Hawk games was achieved.
Please don't forget to include my name if you share my work around. Credit where it is due.

Dev. Console: SCPH-7000 with SCPH-7501 ROM, MM3, PAL color fix, Direct AV ports, DB-9 port for Serial I/O, and a Xplorer FX with Caetla 0.35.

DTL-H2000 PC: Dell Optiplex GX110, Windows 98SE & Windows XP, Pentium III 933MHz, 384MB SDRAM, ATI Radeon 7000 VE 64MB, Soundblaster Audigy, 40GB Seagate HDD, Hitachi Lite-on CD-RW Drive, ZIP 250 and 3.5" Floppy.

User avatar
Shadow
Verified
Admin / PSXDEV
Admin / PSXDEV
Posts: 2670
Joined: Dec 31, 2012
PlayStation Model: H2000/5502
Discord: Shadow^PSXDEV

Post by Shadow » March 22nd, 2019, 1:33 am

LameGuy64 wrote: March 20th, 2019, 11:23 am ...this is how that video screen in one of the Tony Hawk games was achieved.
The way Neversoft managed to pull that off was quite amazing and quite mind blowing. They managed to render the framebuffer inside of itself (almost like a camera-in-camera perspective). They also played music videos on it too. It can be seen in the level "Chicago' in Tony Hawks Pro Skateboarding.
You do not have the required permissions to view the files attached to this post.
Development Console: SCPH-5502 with 8MB RAM, MM3 Modchip, PAL 60 Colour Modification (for NTSC), PSIO Switch Board, DB-9 breakout headers for both RGB and Serial output and an Xplorer with CAETLA 0.34.

PlayStation Development PC: Windows 98 SE, Pentium 3 at 400MHz, 128MB SDRAM, DTL-H2000, DTL-H2010, DTL-H201A, DTL-S2020 (with 4GB SCSI-2 HDD), 21" Sony G420, CD-R burner, 3.25" and 5.25" Floppy Diskette Drives, ZIP 100 Diskette Drive and an IBM Model M keyboard.

User avatar
NITROYUASH
Verified
Serious PSXDEV User
Serious PSXDEV User
Posts: 124
Joined: Jan 07, 2018
I am a: Game Designer
PlayStation Model: SCPH-5502
Location: Russian Federation
Contact:

Post by NITROYUASH » April 9th, 2019, 1:28 am

LoadBS isn't working with Display() for some reason. It starts blinking like a crazy, so i can't sort BS images correctly in loop.

Code: Select all

void Display() {
	FntFlush(-1);
	VSync(0);

	GsSwapDispBuff(); // <- It starts blinking here.   (╯°□°)╯︵ ┻━┻
	GsSortClear(g_iGS_R, g_iGS_G, g_iGS_B, &myOT[ActiveBuffer]);
	GsDrawOt(&myOT[ActiveBuffer]);
}

Orion_
Verified
Legendary Programmer
Legendary Programmer
Posts: 240
Joined: Aug 13, 2012
I am a: Programmer
PlayStation Model: Net Yaroze
Location: France
Contact:

Post by Orion_ » April 10th, 2019, 5:49 am

you should understand what you are doing, this will avoid you headache.

on the PS1, you have 2 screen buffers, one that you drawing in, and one that is currently displayed on screen (while you draw on the other), this avoid flickering.
The function GsSwapDispBuff swap between these 2 buffers.
Now, a BS image is a still image, it's not a sprite that you are rendering in the OT each frame.
The BS image is an image that you are copying once in the VRAM.
If you copy the image ONCE, in only one buffer, each time you will swap to the other buffer, it will display nothing, and the other time it will display the image again, that's why you get "blinking".
now, you must copy the BS image in the 2 buffers, and never clear the screen with GsSortClear, or your image will be erased
Retro game development on Playstation and other consoles http://orionsoft.free.fr/

User avatar
NITROYUASH
Verified
Serious PSXDEV User
Serious PSXDEV User
Posts: 124
Joined: Jan 07, 2018
I am a: Game Designer
PlayStation Model: SCPH-5502
Location: Russian Federation
Contact:

Post by NITROYUASH » April 10th, 2019, 7:52 am

True. That's the problem with the 2'nd screen buffer. I know about double buffer but can't understand how to set the BS image into the 2 screens.

Oh, and i really can't clear the image with BS stuff?

User avatar
LameGuy64
Verified
Psy-Q Enthusiast
Psy-Q Enthusiast
Posts: 388
Joined: Apr 10, 2013
I am a: Hobbyist Game Developer
Motto: Commercial or not, play it!
PlayStation Model: H2000/7000
Location: Philippines
Contact:

Post by LameGuy64 » April 10th, 2019, 12:39 pm

Are you trying to show a sequence of BS'es? The recommended method would be to LoadBS to one part of the screen and use SetDispEnv to display the area the image was loaded to then you load the next BS on another part of the framebuffer which you then display with SetDispEnv. This closely resembles how FMVs are done on the PS1 except FMVs load frames in strips as data is being streamed in from the CD.

If you were to display it like a fullscreen background image load it to some part of the framebuffer and draw it as a texture with sprite primitives.
Please don't forget to include my name if you share my work around. Credit where it is due.

Dev. Console: SCPH-7000 with SCPH-7501 ROM, MM3, PAL color fix, Direct AV ports, DB-9 port for Serial I/O, and a Xplorer FX with Caetla 0.35.

DTL-H2000 PC: Dell Optiplex GX110, Windows 98SE & Windows XP, Pentium III 933MHz, 384MB SDRAM, ATI Radeon 7000 VE 64MB, Soundblaster Audigy, 40GB Seagate HDD, Hitachi Lite-on CD-RW Drive, ZIP 250 and 3.5" Floppy.

User avatar
Shadow
Verified
Admin / PSXDEV
Admin / PSXDEV
Posts: 2670
Joined: Dec 31, 2012
PlayStation Model: H2000/5502
Discord: Shadow^PSXDEV

Post by Shadow » April 28th, 2019, 6:20 am

Love it :lol:

Code: Select all

(╯°□°)╯︵ ┻━┻
Development Console: SCPH-5502 with 8MB RAM, MM3 Modchip, PAL 60 Colour Modification (for NTSC), PSIO Switch Board, DB-9 breakout headers for both RGB and Serial output and an Xplorer with CAETLA 0.34.

PlayStation Development PC: Windows 98 SE, Pentium 3 at 400MHz, 128MB SDRAM, DTL-H2000, DTL-H2010, DTL-H201A, DTL-S2020 (with 4GB SCSI-2 HDD), 21" Sony G420, CD-R burner, 3.25" and 5.25" Floppy Diskette Drives, ZIP 100 Diskette Drive and an IBM Model M keyboard.

Post Reply

Who is online

Users browsing this forum: No registered users and 3 guests