BatteryCheck DevBlog - PlayStation 1: We have a title screen!!!!

After many hours of studying the examples included with PSn00bSDK I had an idea of how to load image data into VRAM. I confirmed the conclusions on the PSX.DEV discord to make sure I got it right. Then it still took me a few days to find the time to actually try it out....because before anything could be loaded I needed the title screen image to be resized, split into two halves and have the halves dumped to binary in an 8bit index palette format they both share! Yeah...the PS1 is not so easy but I might be doing it in awkward ways to! ^_^

I got some help in the conversion from ChatGPT to construct me a python script to resize and dump the .bin files I needed. It took me about two hours of almost yelling at that thing....telling it whats wrong, posting the runtime errors, saying the colors were wrong...black and white...or completely messed up beyond recognition. In the end I took a few steps back...went through it again....and then finally got this glorious result!!! Ain't it beautiful! :D

screen5.jpg

In case you're wondering what the attempts looked like, here are a few screenshots of the process:
vram2.jpg
Here you see the PCSX-Reduc VRAM viewer with the two frame buffers on the left and my first attempt at loading data on the right. You can kind off make out the shape of the title screen if you look closely.

screen2.jpg
And here I finally managed to get the pixels on screen...just not entirely correct. Aside from the obviously funky colors the images looked like half their intended size. The PS1 VRAM has very specific ways to address and align graphics! Making it a kind of frustrating experience to diagnose what's wrong...if you don't know why you give half the commands to draw something on screen. Well...maybe not half but a couple are just....confusing!

screen3.jpg
Now that is more like it...at least in the sense that the pixels are aligned and drawn correctly. The shape of the image is recognizable but the colors are still kinda funky! I did not take more screenshots in between but it was about 1,5 hours of yelling at ChatGPT that it did not do what I asked....saying what to change and such. Yes...I could have figured it out on my own. but not even close to the 1,5 hours it took me now! ^_^ Most developers are lazy...or just me...and we don't like to ACTUALLY do things we don't like. So repetitive stuff or new complex things scare us (me). Having ChatGPT as a new helper is amazing! Yet...frustrating at the same time! At some point it looked like static noise....full black and white and a few other funky things.

If you ask it to do one thing...it "forgets" it also had other requirements to fulfill. I once told it to re-read everything again....make sure my intention was clear! It took a little longer to get the response after that. But it even made a comment on that later....based on your intentions.....hahahahaha. So it understood that what it previously thought I wanted was wrong...and hopefully now it was correct. In short...the direction was better but I still got many errors! It kept giving me code that broke at runtime! I told my specific python version...and after that things got better. Lesson learned: always say the exact version numbers you are working with so generated code is more correct. sometimes. mostly.

The conclusion is that ChatGPT is amazing and useful...but nowhere near capable enough to replace real human developers. yet! It's both scary and wonderful. Where does this technology lead us! For me...it's just a way to write stupid little scripts faster and just "think" my way through it instead of wasting hours trying to learn specific parameters and libraries. Those were the old days...this is the now. But it's still helpful to understand the code that is generated in case you even need to change it!

I have once studied (in my spare time) how assembly worked on older processors. Specifically the atari 800XL with it's 6502 CPU. It was simple enough to understand. Then when I was working on the Sega genesis I learned a bit of 68k assembly. Just enough to see whats happening in ASM code and make adjustments where needed. Or do simple things faster than was possible in C. Make functions in ASM callable from C and that kind of stuff. It was just playing arround but it helped give me insight into what the compiler is doing and what code is generated. Right...also did that! Have the compiler save the ASM code it generated from the C code I wrote to learn from it.

The point is that even if it's a complex subject...like ASM or a script doing things you don't full understand...or want to understand. It does help if you understand the basic underlying principles and components. Because unless you do...you can't tell a tool like ChatGPT what's wrong and how you actually want things done!

In the end I got this final screen:
screen4.jpg
That's basically the same as the first image but with the VRAM viewer on the side. The reason those two rectangles are looking weird and funky is that they are index picture data and the "view" was set as 16bit colors. If you look closely you will also notice a gap between them. This is because the "texture" in PlayStation 1 VRAM need to be aligned by 64 pixels on the X axis. And I think it was 256 in the Y. You then point the GPU to a "TexturePage" and treat that area as a 256x256 texture. It's a little weird at first..and it still is. But at least I got it working now and I can move on to other things to make my port happen! It's going to take many more trials like this to get there though. But this is the very first step in making textures work!

What should be easy to do is change the original input image and have the splash screens show. I think there might be just enough space in VRAM to get three of the splash screens in there. It's not optimized so it wastes a bit of VRAM with that gap between them. But the next "demo" will be a "slideshow" of the title screen, level 1, level 2, level3 and the credit screen. Although...I am not sure I am allowed to use that last one. For a simple demo it's probably fine...and I need to mod it to have it say "programming by Archerite" ofcourse :D Only graphics, sounds and design are still by the original creators obviously.

Hope it was interesting to read. Thank you if you read it all! :D



TL;DR: I made progress in porting my homebrew game BatteryCheck to the PlayStation 1.

Comments

Can it really be that hard to make the PS1 show a still picture??
It was the first console to be overloaded with garbage grade shovelware. Very much like the DS and the Wii later. I'd expected it one of the easiest to program (compared to the Sega Saturn with the multiple processors and the also complex N64).
 
  • Like
Reactions: Archerite
Can it really be that hard to make the PS1 show a still picture??
It was the first console to be overloaded with garbage grade shovelware. Very much like the DS and the Wii later. I'd expected it one of the easiest to program (compared to the Sega Saturn with the multiple processors and the also complex N64).
A true still picture probably not. You just load your image in VRAM and point the GPU to it and "end the frame". But I decided to make the "image" show as a "sprite" so I can learn two things at once. At the same time I wanted to save some VRAM right away by using a palette indexed texture with a Color LookUp Table (CLUT).

The CLUT is just a simple "image" of 1x256 pixels in the PS1's RGB 5551 format. Then for your sprite you point where in memory this CLUT is. But addressing the VRAM is a bit odd. Instead of a byte offset like every single other GPU and computer I have seen.....the PS1 is "special". You address VRAM in X and Y coordinates. Not in pixel incements but alligned on X by 64 pixels and on Y I think it was 256. It will happily load data if you don't follow those rules...but when displaying it you will learn the hard way it was wrong. ^_^

Part of the issue and where time was lost was that the pixels dumped by the python script (written mostly by ChatGPT) did not use the correct CLUT and even used the wrong byte ordering. That's what made those funky colors in the screenshots.

The second difficulty came in the form of not understanding the examples correctly....and the stupid Sony PS1 SDK way of doing things. That PSn00bSDK follows for compatibility reasons but have implemented from scratch I believe.

Also, you don't just "upload a texture" into VRAM. You do a litteral LoadImage() and give that a rectangle(X,Y,W,H) on where to put that "image" in VRAM. And to make things fun...the W (width) is divided by half! But only in VRAM. When you specify screen coordinates it's "normal".

Here is what's required to actually draw those two halves...which are technically two sprites of 160x240 pixels in size:

C:
        SPRT *sprt = (SPRT *) new_primitive(&ctx, 1, sizeof(SPRT));
        setSprt( sprt );
        setXY0( sprt, 0, 0 );
        setRGB0( sprt, 255, 255, 255 );
        setShadeTex( sprt,1 );
        setUV0( sprt, 0, 0 );
        setClut( sprt, clut.x, clut.y);
        sprt->h = 240;
        sprt->w = 160;

        DR_TPAGE *tpage2 = (DR_TPAGE *) new_primitive(&ctx, 1, sizeof(DR_TPAGE));
        setDrawTPage(tpage2,1,0, getTPage(1,1,img.x-128,img.y) );

        SPRT *sprt1 = (SPRT *) new_primitive(&ctx, 1, sizeof(SPRT));
        setSprt( sprt1 );
        setXY0( sprt1, 160, 0 );
        setRGB0( sprt1, 255, 255, 255 );
        setShadeTex( sprt1,1 );
        setUV0( sprt1, 0, 0 );
        setClut( sprt1, clut.x, clut.y);
        sprt1->h = 240;
        sprt1->w = 160;

        DR_TPAGE *tpage3 = (DR_TPAGE *) new_primitive(&ctx, 1, sizeof(DR_TPAGE));
        setDrawTPage(tpage3,1,0, getTPage(1,1,img.x,img.y) );

And because that is imposible to read and remember for the next sprites I already made a few inline functions and macro's to simplify this a lot:

C:
static inline void psx_setTexPage(RenderContext *ctx, RECT tex) {
//static inline void psx_setTexPage(DR_TPAGE *tpage, RECT tex) {
  DR_TPAGE *tpage = (DR_TPAGE *) new_primitive(ctx, 1, sizeof(DR_TPAGE));
  setDrawTPage(tpage,1,0, getTPage(1,1,tex.x,tex.y) );
}

static inline void psx_drawTextureI(RenderContext *ctx, RECT img, RECT clut, int x, int y) {
  SPRT *sprt = (SPRT *) new_primitive(ctx, 1, sizeof(SPRT));
  setSprt( sprt );
  setXY0( sprt, x, y );
  setRGB0( sprt, 255, 255, 255 );
  setShadeTex( sprt,1 );
  setUV0( sprt, 0, 0 );
  setClut( sprt, clut.x, clut.y);
  sprt->w = img.w * 2;
  sprt->h = img.h;
}
#define psx_drawTexture(img,x,y) { psx_drawTextureI(&ctx,img,clut,x,y);}
And then in the main loop only this is required:
C:
    psx_drawTexture(imgL, 0, 0);
    psx_setTexPage(&ctx,imgL);

    psx_drawTexture(imgR, 160, 0);
    psx_setTexPage(&ctx,imgR);

It's still got more code then I like but that's because it's a hacked "template" from the examples I am messing with right now. It's possible to use the lower level stuff more directly when I integrate this into my game engine "platform drivers". Once it's in there nobody ever needs to see it again. ^_^:D

But as you can see...hopefully...yeah it took a bit of effort to get something on screen in the correct colors.
 
  • Like
Reactions: KleinesSinchen
Sega Saturn with the multiple processors and the also complex N64).
I have not looked to much into the sega saturn yet. But from what I know each of those processors does have a specific function and purpose. I don't know if the actual textures and drawing them is similar or different than the PS1. I did read somewhere that the saturn had "software 3D" I think. And I have seen some video's from the writer of SonicR how he did certain tricks on the saturn.

I don't have a saturn myself...and the current "retro inflated prices" will keep it that way for a long while I am afraid.
 
  • Like
Reactions: KleinesSinchen
The technical explanation is giving me a headache right now. I'll have to read this another time.

I have not looked to much into the sega saturn yet. But from what I know each of those processors does have a specific function and purpose. I don't know if the actual textures and drawing them is similar or different than the PS1. I did read somewhere that the saturn had "software 3D" I think.
Random fragment: Using quadrilaterals (four sided polygons) instead of triangles. Don't ask me further.

And I have seen some video's from the writer of SonicR how he did certain tricks on the saturn.
"Coding Secrets" by Jon Burton would have been my next link… good you know it already.


I don't have a saturn myself...and the current "retro inflated prices" will keep it that way for a long while I am afraid.
I' not starting anything anymore.
=========

Off-topic:
The following CD-R passes the harsh check of SecuROM v7.x on almost all drives (these copies pass literally all drives for SecuROM v4.8 and v5.x). Unlikely for a computer to notice it is not the legit CD. This is a Verbatim AZO, means very obvious for the human eye that this a CD-R (blue/green data side).
I could have used a "Silver" or "Diamond" CD-R, which almost looks like a pressed one for humans.
You were right: Ink printer labeled CD-R bootlegs can be scary because of their high quality. Combined with top-notch reproduction of the copy protections this makes it even harder.
DungeonSiegeII_BrokenWorld.jpg
 
  • Like
Reactions: Archerite
And then...I broke it again! :cry:

I did figure out how to get all the splashscreens in VRAM at once and display the correct pixels...but the palette's are a little broken. Not as much as before but it seems like some colors are missing! And my helper ChatGPT is not that helpfull and keeps giving me the same code.

also complex N64
I completly forgot to reply to this specific bit. Yes, the N64 is really complex and limited in it's own way. But it has a few mayor advantage over the PS1. It has 4-8MB of fast RAM which I think is shared with the GPU. That fact alone means once something is in memory in the GPU format it can just stay there and you don't have to swap it out for something else later. And also the EverDrive which can give up to "64MB of RAM/ROM space" and if needed even an SD card for storage.

The PS1 has only 2MB of Main RAM and 1MB of VRAM. On top of that the VRAM is accessed in a weird way and the framebuffers are also in here. Add in double buffering and you "loose" quite a lot for textures and sprites. But also the way you have to display stuff from VRAM is...different. It's kind of familiar but not exactly how you expect.
"Coding Secrets" by Jon Burton would have been my next link… good you know it already.
Offcourse I know Coding Secrets! I love that channel although I did not watch as much anymore. Did he stop making these video's? Especially the savings done for Sonic 3D and Mickey Mania's tower scene were amazing! Making use of every little hardware trick to save memory but still have interesting visuals. It's a true art form if you ask me.

The following CD-R passes the harsh check of SecuROM v7.x on almost all drives (these copies pass literally all drives for SecuROM v4.8 and v5.x). Unlikely for a computer to notice it is not the legit CD. This is a Verbatim AZO, means very obvious for the human eye that this a CD-R (blue/green data side).
I could have used a "Silver" or "Diamond" CD-R, which almost looks like a pressed one for humans.
You were right: Ink printer labeled CD-R bootlegs can be scary because of their high quality. Combined with top-notch reproduction of the copy protections this makes it even harder.
indeed if they can fool the copy protections then it's even harder to spot a fake for a non technical person. It's going to take a lot to fool me into thinking a burned disc with a printed label is authentic. But considering the alternative for your own backups...I would say they are an awesome option to have.

That code looks scary af.
I know right! :D But I am already stating to get the hang of the basic stuff and it does kind of remind me of other 3D GPU's. The function name in the sprite definition are pretty much self explanitory if you used OpenGL or GX on Wii/Gamecube. But the way a texture is loaded/bound/activated is really weird. Instead of before your vertices you do that after! Seems the GPU reads the object table in reverse or something.

I already changed a lot more and added even more macro's! I can now use single line commands to: add an image in the CMake file (but it consist of three actual files), load the exports, load the image into VRAM and finally to display it. Just by referring to it by the name the macro's expand into the actual objects and function calls. And because they are macro's and inline functions there is no speed penalty to using it this way. :D

I am just really sad I broke the palette's somehow. But I am sure I'll figure it out....even if I would have to start from scratch with that conversion script! Even with "Broken palette's" I can still test and play with how the textures work. And I found out that a texture has to start at a 128 pixel (64 byte) boundary in VRAM, other wise you get weird behaviour like drawing a part of the previous image. Because it jumps back to the nearest boundary I guess.

Porting BatteryCheck may not be as straight forward as on modern systems but I am still learning a lot about the PlayStation 1, which is what is most important to me. And also...once the port is getting allong I can apply similar changes to the currently incomplete ports. The most notable is going to be the PSP! The game is nearly completly ported to it. Textures work, lots of RAM and I think even VRAM is fine. The only limitation I ran into was that textures had to be 256x256 pixels and mine are/were 1024x1024 and it was not so easy to change that. This is in part what I want to achieve with the PS1 port.

But also not having an SD card (or similar) for storage makes things fun! The CD has a huge capacity but's very slow to access data from. You have to plan what data is loaded, and when. And it's even possible to specify WHERE on the physical disk data is stored. To minimize seek time and have audio and level data interleaved for example. It's both cool and really complex.
 
Not enough progress yet to make it worthy of a new blog post...but I do want to share these results:

screen-16.png
This is the VRAM viewer in the emulator...and the entire tileset has been resized into 16x16 tiles, reordered to fit into the weird PS1 texture sizes, and loaded into VRAM. I use a ton of macro's to load and setup stuff. And my new helper (ChatGPT) did most of the coding but I told it what to do and what to correct. It saved me hours if not days or even weeks of debugging to make this happen!

And to show the tiles are usable I handdrawn this stupid thing:
screen-15.png

C:
        for(int i=0;i<18;i++) {
          drawTileX(5 ,i,5);
          drawTileX(1 ,i,6);
        }
        drawTileX(6 ,18,5);
        drawTileX(7 ,19,5);

Hidden in macro's and inline functions...but this is what's needed in the end to draw that platform and it's edge on the right. I have not ported anything from my existing gameengine yet and have only been testing loading and converting the graphics. Normally the "drawTile()" function would be called based on the actual tilemap in the game files. Which...I also need to port over to work on the PS1. A big challenge is it's pure size of 1MB might already be too much for the system RAM to handle! At least...on real hardware where there is only 2MB. In the emulator I selected the debug option to get 8MB of RAM. This current exe however with all graphics from the tileset and two splashscreens is just 436kb!

I have settled for the BOX resample algorithm and I do think it looks "good enough" to be drawn on the PS1's GPU. I have not tested on real hardware yet for various reasons. One is this demo is too unfinished to justify burning a disk. And another is that I still need to solder a proper serial to usb cable. And I don't even have a CRT nearby to really make these graphics pop!

All things considered I do think it's making good progress. :D
 

Blog entry information

Author
Archerite
Views
311
Comments
7
Last update

More entries in Personal Blogs

More entries from Archerite

General chit-chat
Help Users
    SylverReZ @ SylverReZ: https://www.youtube.com/watch?v=HHPUzBRjg1E