Repository: ABelliqueux/nolibgs_hello_worlds Branch: main Commit: 48790325a9e3 Files: 186 Total size: 40.3 MB Directory structure: gitextract_v4hb7egi/ ├── .github/ │ └── workflows/ │ ├── Linux-build-archive.yml │ ├── linux-build.yml │ ├── macos-build.yml │ └── windows-build.yml ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── TIM/ │ ├── README.md │ ├── TIM16.tim │ ├── TIM4.tim │ ├── TIM8.tim │ ├── bousai.tim │ └── cubetex.tim ├── VAG/ │ ├── 0_come.vag │ ├── 1_cuek.vag │ ├── 2_erro.vag │ ├── 3_hehe.vag │ ├── 4_m4a1.vag │ ├── 5_punc.vag │ ├── 7_wron.vag │ ├── 8_yooo.vag │ ├── README.md │ ├── hello.vag │ ├── hello_poly.vag │ └── poly.vag ├── common.mk ├── hello_2pads/ │ ├── Makefile │ └── hello_2pads.c ├── hello_bs/ │ ├── Makefile │ ├── README.md │ ├── bs/ │ │ ├── bace.bs │ │ ├── bace.rgb │ │ ├── bace.tim │ │ └── bace.yuv │ ├── hello_bs.c │ ├── isoconfig.xml │ └── system.cnf ├── hello_cd/ │ ├── Makefile │ ├── README.md │ ├── hello_cd.c │ ├── isoconfig.xml │ └── system.cnf ├── hello_cdda/ │ ├── Makefile │ ├── README.md │ ├── hello_cdda.c │ ├── isoconfig.xml │ └── system.cnf ├── hello_cube/ │ ├── Makefile │ ├── cube.c │ └── hello_cube.c ├── hello_cubetex/ │ ├── Makefile │ ├── cubetex.c │ └── hello_cubetex.c ├── hello_cubetex_stp/ │ ├── Makefile │ ├── README.md │ ├── TIM/ │ │ ├── stpOn8bpp.tim │ │ ├── stpOnBlack.tim │ │ └── stpOnNonBlack.tim │ ├── cubetex.c │ └── hello_cubetex_stp.c ├── hello_font/ │ ├── Makefile │ ├── README.md │ ├── fnt.tim │ └── hello_font.c ├── hello_fx/ │ ├── Makefile │ ├── TIM/ │ │ ├── bg.tim │ │ ├── cube.tim │ │ └── sky.tim │ ├── cubetex.c │ └── hello_fx.c ├── hello_gte_opti/ │ ├── Makefile │ └── hello_gte_opti.c ├── hello_light/ │ ├── Makefile │ ├── cube.c │ └── hello_light.c ├── hello_mod/ │ ├── HIT/ │ │ ├── STAR.HIT │ │ └── STAR.mod │ ├── Makefile │ ├── README.md │ ├── hello_mod.c │ └── src/ │ ├── mod.c │ └── mod.h ├── hello_multi_vag/ │ ├── Makefile │ ├── README.md │ └── hello_multi_vag.c ├── hello_multi_xa/ │ ├── Makefile │ ├── README.md │ ├── hello_multi_xa.c │ ├── isoconfig.xml │ ├── system.cnf │ └── xa/ │ ├── 5_come.xa │ ├── 5_erro.xa │ ├── 5_sile_h.xa │ ├── 6_cuek.xa │ ├── 6_hehe.xa │ ├── 6_sile_h.xa │ ├── 6_wron.xa │ ├── 7_m4a1.xa │ ├── 7_punch.xa │ ├── 7_sile_h.xa │ ├── 8_yooo.xa │ ├── channel5.xa │ ├── channel6.xa │ ├── channel7.xa │ ├── channel8.xa │ ├── interleave8.txt │ └── vert8.xa ├── hello_ovl_exec/ │ ├── Makefile │ ├── README.md │ ├── common.h │ ├── hello_ovl_exec.c │ ├── hello_ovl_world/ │ │ ├── Makefile │ │ ├── hello_ovl_world.c │ │ └── hello_world.c │ ├── hello_poly/ │ │ ├── Makefile │ │ ├── hello_ovl_poly.c │ │ └── hello_poly.c │ ├── hello_tile/ │ │ ├── Makefile │ │ ├── hello_ovl_tile.c │ │ └── hello_tile.c │ ├── isoconfig.xml │ ├── overlay.ld │ └── system.cnf ├── hello_pad/ │ ├── Makefile │ └── hello_pad.c ├── hello_poly/ │ ├── Makefile │ └── hello_poly.c ├── hello_poly_ft/ │ ├── Makefile │ └── hello_poly_ft.c ├── hello_poly_fun/ │ ├── Makefile │ └── hello_poly_fun.c ├── hello_poly_gt/ │ ├── Makefile │ └── hello_poly_gt.c ├── hello_poly_gt_tw/ │ ├── Makefile │ └── hello_poly_gt_tw.c ├── hello_poly_inline/ │ ├── Makefile │ └── hello_poly_inline.c ├── hello_poly_stp/ │ ├── Makefile │ ├── README.md │ ├── TIM/ │ │ ├── stpOnAlpha.tim │ │ ├── stpOnAlphaI.tim │ │ ├── stpOnBlack.tim │ │ ├── stpOnCol.tim │ │ ├── stpOnColIndex.tim │ │ └── stpOnNonBlack.tim │ └── hello_poly_stp.c ├── hello_sio/ │ ├── Makefile │ └── hello_sio.c ├── hello_sprt/ │ ├── Makefile │ └── hello_sprt.c ├── hello_spu_readback/ │ ├── Makefile │ ├── README.md │ ├── hello_spu_readback.c │ ├── isoconfig.xml │ └── system.cnf ├── hello_str/ │ ├── Makefile │ ├── README.md │ ├── hello_str.c │ ├── isoconfig.xml │ ├── str/ │ │ └── copyings.str │ └── system.cnf ├── hello_strplay/ │ ├── Makefile │ ├── README.md │ ├── hello_strplay.c │ ├── isoconfig.xml │ ├── str/ │ │ └── copyings.str │ ├── strplay.c │ └── system.cnf ├── hello_tile/ │ ├── Makefile │ └── hello_tile.c ├── hello_vag/ │ ├── Makefile │ ├── README.md │ └── hello_vag.c ├── hello_world/ │ ├── Makefile │ └── hello_world.c ├── hello_xa/ │ ├── Makefile │ ├── README.md │ ├── hello_xa.c │ ├── isoconfig.xml │ ├── system.cnf │ └── xa/ │ ├── inter4.xa │ ├── inter8.xa │ ├── interleave4.txt │ └── interleave8.txt └── includes/ └── CPUMAC.H ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/Linux-build-archive.yml ================================================ name: Linux archive build on: push: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - name: Get repo archive run: | wget https://github.com/ABelliqueux/nolibgs_hello_worlds/releases/download/v0.11/nolibgs_hello_worlds.zip 7z x nolibgs_hello_worlds.zip -o${{github.workspace}} - name: Install mipsel toolchain run: | sudo apt-get update sudo apt-get install gcc-mipsel-linux-gnu g++-mipsel-linux-gnu binutils-mipsel-linux-gnu p7zip cmake build-essential libtinyxml2-10 libtinyxml2-dev - name: Clone mkpsxiso uses: GuillaumeFalourd/clone-github-repo-action@v1 with: owner: 'Lameguy64' repository: 'mkpsxiso' - name: Build mkpsxiso run: | sudo chown -R runner:docker ${{github.workspace}}/mkpsxiso/ mkdir ${{github.workspace}}/mkpsxiso/build cmake -DCMAKE_BUILD_TYPE=Release -S ${{github.workspace}}/mkpsxiso/ -B ${{github.workspace}}/mkpsxiso/build cmake --build ${{github.workspace}}/mkpsxiso/build echo "${{github.workspace}}/mkpsxiso/build" >> $GITHUB_PATH - name: Get converted libs run: | wget http://psx.arthus.net/sdk/Psy-Q/psyq-4.7-converted-full.7z 7z x psyq-4.7-converted-full.7z -o${{github.workspace}}/nolibgs_hello_worlds/psyq - name: Make all run: | cd ${{github.workspace}}/nolibgs_hello_worlds make all ================================================ FILE: .github/workflows/linux-build.yml ================================================ name: Linux build on: push: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install mipsel toolchain run: | sudo apt-get update sudo apt-get install gcc-mipsel-linux-gnu g++-mipsel-linux-gnu binutils-mipsel-linux-gnu p7zip cmake build-essential libtinyxml2-10 libtinyxml2-dev - name: Fetch submodules run: git submodule update --init --recursive - name: Clone mkpsxiso uses: GuillaumeFalourd/clone-github-repo-action@v1 with: owner: 'Lameguy64' repository: 'mkpsxiso' - name: Build mkpsxiso run: | sudo chown -R runner:docker ${{github.workspace}}/mkpsxiso/ git -C ${{github.workspace}}/mkpsxiso/ submodule update --init --recursive mkdir ${{github.workspace}}/mkpsxiso/build cmake -DCMAKE_BUILD_TYPE=Release -S ${{github.workspace}}/mkpsxiso/ -B ${{github.workspace}}/mkpsxiso/build cmake --build ${{github.workspace}}/mkpsxiso/build echo "${{github.workspace}}/mkpsxiso/build" >> $GITHUB_PATH - name: Get converted libs run: | wget http://psx.arthus.net/sdk/Psy-Q/psyq-4.7-converted-full.7z 7z x psyq-4.7-converted-full.7z -o./psyq - name: Make all run: make all ================================================ FILE: .github/workflows/macos-build.yml ================================================ name: macOS build on: push: branches: [ main ] jobs: build: runs-on: macOS-latest steps: - uses: actions/checkout@v2 - name: Fetch submodules run: git submodule update --init --recursive - name: Install tinyxml2 run : | brew update --verbose brew install tinyxml2 - name: Install mipsel binutils run: | wget https://raw.githubusercontent.com/grumpycoders/pcsx-redux/a899d09d81cf602ef48e51cda09a6c62638fa5c5/tools/macos-mips/mipsel-none-elf-binutils.rb -O ${{github.workspace}}/mipsel-none-elf-binutils.rb brew install ${{github.workspace}}/mipsel-none-elf-binutils.rb --debug - name: Upload mipsel-binutils uses: actions/upload-artifact@v2 with: name: mipsel-none-elf-binutils-macos path: /usr/local/Cellar/mipsel-none-elf-binutils/ - name: Install mipsel gcc run: | wget https://raw.githubusercontent.com/grumpycoders/pcsx-redux/a899d09d81cf602ef48e51cda09a6c62638fa5c5/tools/macos-mips/mipsel-none-elf-gcc.rb -O ${{github.workspace}}/mipsel-none-elf-gcc.rb brew install ${{github.workspace}}/mipsel-none-elf-gcc.rb --debug - name: Upload mipsel-gcc uses: actions/upload-artifact@v2 with: name: mipsel-none-elf-gcc-macos path: /usr/local/Cellar/mipsel-none-elf-gcc/ - name: Build mkpsxiso run: | wget https://github.com/Lameguy64/mkpsxiso/archive/refs/heads/master.zip -O ${{github.workspace}}/master.zip 7z x ${{github.workspace}}/master.zip -o${{github.workspace}} awk 'n>=4 { print a[n%7] } { a[n%7]=$0; n=n+1 }' ${{github.workspace}}/mkpsxiso-master/CMakeLists.txt >> ${{github.workspace}}/mkpsxiso-master/CMakeLists.txt.tmp mv -f ${{github.workspace}}/mkpsxiso-master/CMakeLists.txt.tmp ${{github.workspace}}/mkpsxiso-master/CMakeLists.txt mkdir ${{github.workspace}}/mkpsxiso-master/build cmake -DCMAKE_BUILD_TYPE=Release -S ${{github.workspace}}/mkpsxiso-master -B ${{github.workspace}}/mkpsxiso-master/build cmake --build ${{github.workspace}}/mkpsxiso-master/build echo "${{github.workspace}}/mkpsxiso-master/build" >> $GITHUB_PATH - name: Upload mkpsxiso-macos uses: actions/upload-artifact@v2 with: name: mkpsxiso-macos path: ${{github.workspace}}/mkpsxiso-master/build - name: Get converted libs run: | wget http://psx.arthus.net/sdk/Psy-Q/psyq-4.7-converted-full.7z 7z x psyq-4.7-converted-full.7z -o./psyq - name: Make all run: make all ================================================ FILE: .github/workflows/windows-build.yml ================================================ name: Windows build on: push: branches: [ main ] jobs: build: runs-on: windows-latest steps: - uses: actions/checkout@v2 - name: Fetch submodules run: git submodule update --init --recursive - name: Get prebuilt stuff run: | C:\msys64\usr\bin\wget.exe http://static.grumpycoder.net/pixel/mips/g++-mipsel-none-elf-11.2.0.zip C:\msys64\usr\bin\wget.exe http://lameguy64.github.io/mkpsxiso/mkpsxiso-1.20.zip C:\msys64\usr\bin\wget.exe http://psx.arthus.net/sdk/Psy-Q/psyq-4.7-converted-full.7z 7z.exe x g++-mipsel-none-elf-11.2.0.zip -o"$GITHUB_WORKSPACE\mipsel" 7z.exe x mkpsxiso-1.20.zip -o"$GITHUB_WORKSPACE\mkpsxiso" 7z.exe x psyq-4.7-converted-full.7z -o"D:\a\nolibgs_hello_worlds\nolibgs_hello_worlds\psyq" echo "$GITHUB_WORKSPACE/mipsel/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append echo "$GITHUB_WORKSPACE/mkpsxiso" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append echo $GITHUB_PATH - name: Make all run: make all ================================================ FILE: .gitmodules ================================================ [submodule "thirdparty/nugget"] path = thirdparty/nugget url = https://github.com/pcsx-redux/nugget.git ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: Makefile ================================================ hello_2pads: $(MAKE) -C hello_2pads hello_bs: $(MAKE) -C hello_bs hello_cd: $(MAKE) -C hello_cd hello_cdda: $(MAKE) -C hello_cdda hello_cd_exec: $(MAKE) -C hello_cd_exec hello_cube: $(MAKE) -C hello_cube hello_cubetex: $(MAKE) -C hello_cubetex hello_cubetex_stp: $(MAKE) -C hello_cubetex_stp hello_font: $(MAKE) -C hello_font hello_fx: $(MAKE) -C hello_fx hello_gte_opti: $(MAKE) -C hello_gte_opti hello_light: $(MAKE) -C hello_light hello_mod: $(MAKE) -C hello_mod hello_multi_vag: $(MAKE) -C hello_multi_vag hello_multi_xa: $(MAKE) -C hello_multi_xa hello_ovl_exec: $(MAKE) -C hello_ovl_exec hello_pad: $(MAKE) -C hello_pad hello_poly: $(MAKE) -C hello_poly hello_poly_ft: $(MAKE) -C hello_poly_ft hello_poly_fun: $(MAKE) -C hello_poly_fun hello_poly_gt: $(MAKE) -C hello_poly_gt hello_poly_gt_tw: $(MAKE) -C hello_poly_gt_tw hello_poly_inline: $(MAKE) -C hello_poly_inline hello_poly_stp: $(MAKE) -C hello_poly_stp hello_sio: $(MAKE) -C hello_sio hello_sprt: $(MAKE) -C hello_sprt hello_spu_readback: $(MAKE) -C hello_spu_readback hello_str: $(MAKE) -C hello_str hello_strplay: $(MAKE) -C hello_strplay hello_tile: $(MAKE) -C hello_tile hello_vag: $(MAKE) -C hello_vag hello_world: $(MAKE) -C hello_world hello_xa: $(MAKE) -C hello_xa hello_xa_streaming: $(MAKE) -C hello_xa_streaming clean: $(MAKE) -C hello_2pads clean $(MAKE) -C hello_cube clean $(MAKE) -C hello_cubetex clean $(MAKE) -C hello_cubetex_stp clean $(MAKE) -C hello_poly_fun clean $(MAKE) -C hello_gte_opti clean $(MAKE) -C hello_light clean $(MAKE) -C hello_mod clean $(MAKE) -C hello_multi_vag clean $(MAKE) -C hello_multi_xa clean $(MAKE) -C hello_ovl_exec cleansub $(MAKE) -C hello_pad clean $(MAKE) -C hello_poly clean $(MAKE) -C hello_poly_ft clean $(MAKE) -C hello_poly_stp clean $(MAKE) -C hello_poly_gt clean $(MAKE) -C hello_poly_gt_tw clean $(MAKE) -C hello_poly_inline clean $(MAKE) -C hello_sio clean $(MAKE) -C hello_sprt clean $(MAKE) -C hello_spu_readback cleansub $(MAKE) -C hello_tile clean $(MAKE) -C hello_vag clean $(MAKE) -C hello_world clean $(MAKE) -C hello_cdda cleansub $(MAKE) -C hello_cd cleansub $(MAKE) -C hello_xa cleansub $(MAKE) -C hello_bs cleansub $(MAKE) -C hello_str cleansub $(MAKE) -C hello_strplay cleansub all: $(MAKE) -C hello_2pads $(MAKE) -C hello_cube $(MAKE) -C hello_cubetex $(MAKE) -C hello_cubetex_stp $(MAKE) -C hello_poly_fun $(MAKE) -C hello_gte_opti $(MAKE) -C hello_light $(MAKE) -C hello_mod $(MAKE) -C hello_multi_vag $(MAKE) -C hello_multi_xa $(MAKE) -C hello_ovl_exec $(MAKE) -C hello_pad $(MAKE) -C hello_poly $(MAKE) -C hello_poly_ft $(MAKE) -C hello_poly_gt $(MAKE) -C hello_poly_gt_tw $(MAKE) -C hello_poly_inline $(MAKE) -C hello_sio $(MAKE) -C hello_sprt $(MAKE) -C hello_spu_readback $(MAKE) -C hello_tile $(MAKE) -C hello_vag $(MAKE) -C hello_world $(MAKE) -C hello_cd all $(MAKE) -C hello_cdda all $(MAKE) -C hello_xa all $(MAKE) -C hello_bs all $(MAKE) -C hello_str all $(MAKE) -C hello_strplay all # declare phony rules .PHONY: hello_2pads hello_bs hello_cd hello_cdda hello_cd_exec hello_cube hello_cubetex hello_cubetex_stp hello_font hello_fx hello_gte_opti hello_light hello_mod hello_multi_vag hello_multi_xa hello_ovl_exec hello_pad hello_poly hello_poly_ft hello_poly_fun hello_poly_gt hello_poly_gt_tw hello_poly_inline hello_poly_stp hello_sio hello_sprt hello_spu_readback hello_str hello_strplay hello_tile hello_vag hello_world hello_xa hello_xa_streaming \ clean all ================================================ FILE: README.md ================================================ # Nolibgs Hello Worlds !

3D power ! 3D power ! 3D power !

So you want to begin developping on the original PSX but don't know where to start ? This repo is destined to host a bunch of simple examples, each describing how to do one thing. The code here will be using **Nugget + PsyQ**, the "Official" Sony SDK but with a modern MIPS toolchain. We will not be using libGS, the Extended Graphics Library for the graphic stuff... Instead we'll try to devise methods to reproduce libgs functions. This will not necessarly be more efficient, but we'll learn a lot more stuff ! ## Installation We'll keep things simple for now. If you want to read about more methods to get things up and running, see the wiki's [Installation methods](https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/Installation-methods) section. ### Windows #### MIPS toolchain setup You can setup a pre-built MIPS toolchain by copy-pasting the following into a command prompt: ``` powershell -c "& { iwr https://raw.githubusercontent.com/grumpycoders/pcsx-redux/main/mips.ps1 | iex }" ``` Then, open a new command prompt, and type the following: ``` mips install 11.2.0 ``` #### Nugget + PsyQ setup 1. Download the PsyQ converted libraries here : [http://psx.arthus.net/sdk/Psy-Q/psyq-4.7-converted-full.7z](http://psx.arthus.net/sdk/Psy-Q/psyq-4.7-converted-full.7z) 2. Clone the 'nolibgs_hello_worlds' repo with `git clone https://github.com/ABelliqueux/nolibgs_hello_worlds.git --recursive` or download this [repository's release](https://github.com/ABelliqueux/nolibgs_hello_worlds/releases/download/v0.11/nolibgs_hello_worlds.zip) and extract **`nolibgs_hello_worlds.zip`**'s content to `C:\no_libgs_hello_worlds\` . 3. Extract the content of `psyq-4.7-converted-full.7z` in `C:\no_libgs_hello_worlds\psyq`. You should now have `C:\no_libgs_hello_worlds\psyq\include` and `C:\no_libgs_hello_worlds\psyq\lib` ; ``` no_libgs_hello_worlds ├── common.mk ├── hello_world | ├── hello_world.c | ├── Makefile ├── hello_... └── psyq ├── lib | └── *.a └── include └── *.h ``` 4. Test everything is working by [launching a command prompt](https://www.lifewire.com/how-to-open-command-prompt-2618089), change to the `C:\no_libgs_hello_worlds\` directory with the following command: `cd C:\no_libgs_hello_worlds\`, then type `make` and hit enter. By default, this should build the `hello_world` example, and you should now have a `hello_world.ps-exe` file in `C:\no_libgs_hello_worlds\hello_world`. This a PSX executable that can be run in an emulator like [pcsx-redux](https://github.com/grumpycoders/pcsx-redux/). ### Linux #### Install your distribution's MIPS toolchain In a terminal : On Debian derivatives (Ubuntu, Mint...) : ```bash sudo apt-get install make gcc-mipsel-linux-gnu g++-mipsel-linux-gnu binutils-mipsel-linux-gnu ``` On Arch derivatives (Manjaro), the mipsel environment can be installed from [AUR](https://wiki.archlinux.org/index.php/Aur) : [mipsel-linux-gnu-binutils](https://aur.archlinux.org/packages/mipsel-linux-gnu-binutils/) and [mipsel-linux-gnu-gcc](https://aur.archlinux.org/packages/mipsel-linux-gnu-gcc/) using your [AURhelper](https://wiki.archlinux.org/index.php/AUR_helpers) of choice: ```bash yay -S make mipsel-linux-gnu-binutils mipsel-linux-gnu-gcc ``` #### Nugget + PsyQ setup Let's do it all on the [CLI](https://en.wikipedia.org/wiki/Command-line_interface) ! 1. Install the git client : ```bash sudo apt-get install git ``` 2. Clone this repository : ```bash git clone https://github.com/ABelliqueux/nolibgs_hello_worlds.git --recursive ``` 3. Change to the repo's directory and get the PsyQ converted libraries: ```bash cd nolibgs_hello_worlds wget http://psx.schnappy.xyz/sdk/Psy-Q/psyq-4.7-converted-full.7z 7z x psyq-4.7-converted-full.7z -o./psyq ``` 4. Try your setup : ```bash make ``` By default, this should build the `hello_world` example, and you should now have a `hello_world.ps-exe` file in `./hello_world/`. This a PSX executable that can be run in an emulator like [pcsx-redux](https://github.com/grumpycoders/pcsx-redux/). ### MacOS A [brew](https://brew.sh/) installation script can be found [here.](https://github.com/grumpycoders/pcsx-redux#macos). ## Compilation In a terminal, `cd` to your psxdev setup directory and type `make all` to build all examples in their respective directories. Alternatively, you can use `make example_name` to only build that example, i.e : `make hello_poly`. If you want to remove all the files generated by the compilation process, type `make clean`. ## Upcoming examples * hello_poly_subdiv (polygon subdivision) * hello_rsd (rsd format) # Links and Doc * [Getting started with PSX dev](https://psx.arthus.net/starting.html) * [Ps1 dev ressource center](https://ps1.consoledev.net/) * [PsyQ docs](https://psx.arthus.net/sdk/Psy-Q/DOCS/) * [psxdev.net](http://psxdev.net/) * [psxdev Discord](https://discord.com/invite/N2mmwp) # Credits, thanks, hugs Everything here was learnt from some more talented persons, mainly but not excluding others that hang around on the [psxdev discord](https://discord.com/channels/642647820683444236/642848627823345684) Nicolas Noble, Lameguy64, NDR008, Jaby smoll seamonstah, danhans42, rama, sickle, paul, squaresoft74, and lot mores ! ================================================ FILE: TIM/README.md ================================================ See here for more informations about the TIM fileformat and tools : https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/TIM ================================================ FILE: VAG/README.md ================================================ See here for more informations about the VAG fileformat and tools : https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/VAG ================================================ FILE: common.mk ================================================ # If you change this to exe, you'll have to rename the file ./thirdparty/nugget/ps-exe.ld too. TYPE = ps-exe THISDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) SRCS += $(THISDIR)thirdparty/nugget/common/crt0/crt0.s SRCS += $(THISDIR)thirdparty/nugget/common/syscalls/printf.s CPPFLAGS += -I$(THISDIR)thirdparty/nugget/psyq/include -I$(THISDIR)psyq-4_7-converted/include -I$(THISDIR)psyq-4.7-converted-full/include -I$(THISDIR)psyq/include LDFLAGS += -L$(THISDIR)thirdparty/nugget/psyq/lib -L$(THISDIR)psyq-4_7-converted/lib -L$(THISDIR)psyq-4.7-converted-full/lib -L$(THISDIR)psyq/lib # add support for NDR008's VScode setup CPPFLAGS += -I$(THISDIR)../third_party/psyq/include LDFLAGS += -L$(THISDIR)../third_party/psyq/lib LDFLAGS += -Wl,--start-group LDFLAGS += -lapi LDFLAGS += -lc LDFLAGS += -lc2 LDFLAGS += -lcard LDFLAGS += -lcomb LDFLAGS += -lds LDFLAGS += -letc LDFLAGS += -lgpu LDFLAGS += -lgs LDFLAGS += -lgte LDFLAGS += -lgun LDFLAGS += -lhmd LDFLAGS += -lmath LDFLAGS += -lmcrd LDFLAGS += -lmcx LDFLAGS += -lpad LDFLAGS += -lpress LDFLAGS += -lsio LDFLAGS += -lsnd LDFLAGS += -lspu LDFLAGS += -ltap LDFLAGS += -lcd LDFLAGS += -Wl,--end-group include $(THISDIR)thirdparty/nugget/common.mk define OBJCOPYME $(PREFIX)-objcopy -I binary --set-section-alignment .data=4 --rename-section .data=.rodata,alloc,load,readonly,data,contents -O $(FORMAT) -B mips $< $@ endef # convert TIM file to bin %.o: %.tim $(call OBJCOPYME) # convert VAG files to bin %.o: %.vag $(call OBJCOPYME) # convert HIT to bin %.o: %.HIT $(call OBJCOPYME) ================================================ FILE: hello_2pads/Makefile ================================================ TARGET = hello_2pads SRCS = hello_2pads.c \ include ../common.mk ================================================ FILE: hello_2pads/hello_2pads.c ================================================ // hello_libpad example // // We're using libpad this time. // You can use the classic controller, analog, wheel, gun buttons or mouse // // Schnappy - 12/2020 // // Based on : ../psyq/addons/scea/CNTRL/PAD.C #include #include #include #include #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 #define SCREENYRES 240 #define CENTERX SCREENXRES/2 #define CENTERY SCREENYRES/2 #define MARGINX 32 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 7 // Text Field Height #define OTLEN 8 // Ordering Table Length DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] short db = 0; // index of which buffer is used, values 0, 1 // Pad stuff // Structure for RAW hardware-based light gun position values typedef struct { unsigned short v_count; // Y-axis (vertical scan counter) unsigned short h_count; // H-axis (horizontal pixel clock value) } Gun_Position; // Structure for storing processed controller data typedef struct { int xpos, ypos; // Stored position for sprite(s) int xpos2, ypos2; // controlled by this controller. unsigned char status; // These 8 values are obtained unsigned char type; // directly from the controller unsigned char button1; // buffer we installed with InitPAD. unsigned char button2; unsigned char analog0; unsigned char analog1; unsigned char analog2; unsigned char analog3; } Controller_Data; // All-purpose controller data buffer typedef struct { unsigned char pad[34]; // 8-bytes w/o Multi-Tap, 34-bytes w/Multi-Tap } Controller_Buffer; Controller_Buffer controllers[2]; // Buffers for reading controllers Controller_Data theControllers[8]; // Processed controller data void init(void) { ResetGraph(0); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], 50, 50, 50); setRGB0(&draw[1], 50, 50, 50); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } void get_digital_direction( Controller_Data *c, int buttondata ) // get analog stick values { int i; i = ~(buttondata); if( i & 0x80 ) c->xpos -= 1; if( i & 0x20 ) c->xpos += 1; if( i & 0x40 ) c->ypos += 1; if( i & 0x10 ) c->ypos -= 1; } void read_controller( Controller_Data *c, unsigned char *buf, int port ) // get the raw values from controller { register int mouse_x, mouse_y, x; register Gun_Position *g; c->status = buf[0]; // Copy over raw controller data c->type = buf[1]; c->button1 = buf[2]; c->button2 = buf[3]; c->analog0 = buf[4]; c->analog1 = buf[5]; c->analog2 = buf[6]; c->analog3 = buf[7]; if( buf[0] == 0xff ) // If controller returns BAD status then bail on it. { c->type = 0; return; } // Look at the controller type code & process controller data as indicated switch( c->type ) { case 0x12: // Sony Mouse mouse_x = buf[4]; mouse_y = buf[5]; if( mouse_x & 0x80 ) mouse_x |= 0xffffff80; if( mouse_y & 0x80 ) mouse_y |= 0xffffff80; c->xpos += mouse_x; c->ypos += mouse_y; break; case 0x23: // Namco negCon // Steering wheel // Sankyo Pachinko controler get_digital_direction( c, buf[2] ); break; case 0x53: // Analog 2-stick get_digital_direction( c, buf[2] ); break; case 0x41: // Standard Sony PAD controller get_digital_direction( c, buf[2] ); break; default: // If don't know what it is, treat it like standard controller get_digital_direction( c, buf[2] ); break; } } int main(void) { TILE * PADL; // Tile primitives TILE * TRIGGERL; TILE * PADR; TILE * TRIGGERR; TILE * START, * SELECT; init(); InitPAD(controllers[0].pad, 34, controllers[1].pad, 34); StartPAD(); while (1) { read_controller( &theControllers[0], &controllers[0].pad[0], 0 ); // Read controllers read_controller( &theControllers[1], &controllers[1].pad[0], 1 ); ClearOTagR(ot[db], OTLEN); // D-cross PADL = (TILE *)nextpri; setTile(PADL); setRGB0(PADL, 80, 180, 255); setXY0(PADL, CENTERX - 80, CENTERY); setWH(PADL, 24, 24); addPrim(ot[db], PADL); nextpri += sizeof(TILE); // L1+L2 TRIGGERL = (TILE *)nextpri; setTile(TRIGGERL); setRGB0(TRIGGERL, 255, 0, 0); setXY0(TRIGGERL, CENTERX - 80, CENTERY - 80); setWH(TRIGGERL, 24, 24); addPrim(ot[db], TRIGGERL); nextpri += sizeof(TILE); // /\, X, O, [] PADR = (TILE *)nextpri; setTile(PADR); setRGB0(PADR, 0, 255, 0); setXY0(PADR, CENTERX + 50, CENTERY); setWH(PADR, 24, 24); addPrim(ot[db], PADR); nextpri += sizeof(TILE); // R1+R2 TRIGGERR = (TILE *)nextpri; setTile(TRIGGERR); setRGB0(TRIGGERR, 255, 0, 255); setXY0(TRIGGERR, CENTERX + 50, CENTERY -80); setWH(TRIGGERR, 24, 24); addPrim(ot[db], TRIGGERR); nextpri += sizeof(TILE); // START + SELECT START = (TILE *)nextpri; setTile(START); setRGB0(START, 240, 240, 240); setXY0(START, CENTERX - 16, CENTERY - 36); setWH(START, 24, 24); addPrim(ot[db], START); nextpri += sizeof(TILE); // D-pad switch(theControllers[0].button1){ case 0xDF: // Right PADL->x0 = CENTERX - 64; break; case 0x7F: // Left PADL->x0 = CENTERX - 96; break; case 0xEF: // Up PADL->y0 = CENTERY - 16; break; case 0xBF: // Down PADL->y0 = CENTERY + 16; break; // Start & Select case 0xF7: START->w = 32; START->h = 32;START->x0 -= 4;START->y0 -= 4; // START break; case 0xFE: // SELECT START->r0 = 0; break; // Dualshock L3 + R3 case 0xFD: // L3 TRIGGERL->w += 10; TRIGGERL->h += 10; break; case 0xFB: //R3 TRIGGERR->w += 10; TRIGGERR->h += 10; break; } // Buttons switch(theControllers[0].button2){ case 0xDF: // ⭘ PADR->x0 = CENTERX + 66; break; case 0x7F: // ⬜ PADR->x0 = CENTERX + 34; break; case 0xEF: // △ PADR->y0 = CENTERY - 16; break; case 0xBF: // ╳ PADR->y0 = CENTERY + 16; break; // Shoulder buttons case 0xFB: // L1 TRIGGERL->y0 = CENTERY - 64; break; case 0xFE: // L2 TRIGGERL->y0 = CENTERY - 96; break; case 0xF7: // R1 TRIGGERR->y0 = CENTERY - 96; break; case 0xFD: // R2 TRIGGERR->y0 = CENTERY - 64; break; // Mouse buttons case 0xF4: // Mouse Left click PADL->w += 10; PADL->h += 10; break; case 0xF8: // Mouse Right click PADL->w -= 10; PADL->h -= 10; break; } FntPrint("Hello 2 pads!\n\n"); FntPrint( "Pad 1 : %02x\nButtons:%02x %02x, Stick:%02x %02x %02x %02x\n", theControllers[0].type, // Controller type : 00 == none, 41 == standard, 73 == analog/dualshock, 12 == mouse, 23 == steering wheel, 63 == gun, 53 == analog joystick theControllers[0].button1, // theControllers[0].button2, theControllers[0].analog0, theControllers[0].analog1, theControllers[0].analog2, theControllers[0].analog3 ); FntPrint( "Pad 2 : %02x\nButtons:%02x %02x, Stick:%02x %02x %02x %02x\n", theControllers[1].type, // Controller type : 00 == none, 41 == standard, 73 == analog/dualshock, 12 == mouse, 23 == steering wheel, 63 == gun, 53 == analog joystick theControllers[1].button1, // theControllers[1].button2, theControllers[1].analog0, // R3 horizontal theControllers[1].analog1, // R3 vertical theControllers[1].analog2, // L3 horizontal theControllers[1].analog3 ); // L3 vertical FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_bs/Makefile ================================================ .PHONY: all cleansub all: mkpsxiso -y ./isoconfig.xml cleansub: $(MAKE) clean rm -f hello_bs.cue hello_bs.bin TARGET = hello_bs SRCS = hello_bs.c \ include ../common.mk ================================================ FILE: hello_bs/README.md ================================================ # Loading a BS still image You need [mkpsxiso](https://github.com/Lameguy64/mkpsxiso) in your $PATH to generate a PSX disk image. ## Compile ```bash make all ``` ## Clean directory ```bash make cleansub ``` ## Converting a still image to BS `MC32` can convert these formats to BS : TIM, RGB, YUV. ### Image > TIM with img2tim Convert your image to a 24bpp TIM with [`img2tim`](https://github.com/Lameguy64/img2tim): ```bash img2tim -bpp 24 -o output.tim input.png ``` Then use `MC32` as instructed below. Result : ```bash identify bace.tim bace.tim TIM 320x240 320x240+0+0 8-bit sRGB 230420B 0.000u 0:00.000 ``` ### Image > RGB with imagemagick You can convert your image to RGB with: ```bash convert input.png RGB:output.rgb ``` Result : ```bash identify -size 320x240 -depth 8 RGB:bace.rgb RGB:bace.rgb=>bace.rgb RGB 320x240 320x240+0+0 8-bit sRGB 230400B 0.000u 0:00.003 ``` ### Image > YUV422 UYVY with imagemagick You can convert your image to YUV with: ```bash convert input.png UYVY:output.yuv ``` Result : ```bash dentify -size 320x240 UYVY:bace.yuv UYVY:bace.yuv=>bace.yuv UYVY 320x240 320x240+0+0 8-bit YCbCr 153600B 0.000u 0:00.005 ``` ### TIM/RGB/UYVY > BS conversion Use the [`MC32` tool](http://psx.arthus.net/tools/pimp-psx.zip) conversion tool to import the image, specifying the right dimensions, and convert to `bs` with those settings : **Note that a BS image must have a width and height that is a multiple of 16** ``` Input: RGB, Output: bs MDEC version : 2 Custom: Size in sectors or (2048 * sector number) bytes, Variable frame size ``` ![MC32 bs conversion](https://wiki.arthus.net/assets/mc32-bs-conv.png) ## Sources & Refs img2tim : https://github.com/Lameguy64/img2tim MC32 : http://psx.arthus.net/tools/pimp-psx.zip mdecnote : http://psx.arthus.net/sdk/Psy-Q/DOCS/TECHNOTE/mdecnote.pdf PSX RGB and YUV format : http://psx.arthus.net/sdk/Psy-Q/DOCS/Devrefs/Dataconv.pdf , p.68 YUV pixel format : https://www.fourcc.org/pixel-format/yuv-uyvy/ RGB pixelformat : https://www.fourcc.org/pixel-format/rgb-bi_rgb/ RGB<>YUV conversion formulas : https://www.fourcc.org/fccyvrgb.php ================================================ FILE: hello_bs/hello_bs.c ================================================ // Load a BS file from CD, decompress and display it. // Schnappy 07-2021 #include #include #include #include #include // CD library #include // CODEC library #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 + (VMODE << 4) // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256 #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 8 // margins for text display #define MARGINY 16 #define FONTSIZE 8 * 7 // Text Field Height DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; short db = 0; // index of which buffer is used, values 0, 1 // CD specifics #define CD_SECTOR_SIZE 2048 // Converting bytes to sectors SECTOR_SIZE is defined in words, aka int #define BtoS(len) ( ( len + CD_SECTOR_SIZE - 1 ) / CD_SECTOR_SIZE ) // Name of file to load static char * loadFile; // libcd's CD file structure contains size, location and filename CdlFILE filePos = {0}; //~ struct EXEC * exeStruct; // Define start address of allocated memory // Let's use an array so we don't have to worry about using a memory segment that's already in use. static unsigned char ramAddr[0x40000]; // https://discord.com/channels/642647820683444236/663664210525290507/864936962199781387 // We could also set a memory address manually, but we have to make sure this won't get in the way of other routines. // void * ramAddr = (void *)0x80030D40; // Load data to this buffer u_long * dataBuffer; // Those are not strictly needed, but we'll use them to see the commands results. // They could be replaced by a 0 in the various functions they're used with. u_char CtrlResult[8]; // Value returned by CDread() - 1 is good, 0 is bad int CDreadOK = 0; // Value returned by CDsync() - Returns remaining sectors to load. 0 is good. int CDreadResult = 0; // BS decompression // Store size of uncompressed data long bsBufferSize; // Allocated memory address void * bsWorkBuffer; // Define image draw area RECT bsDrawArea = { 0, 0, 16, SCREENYRES}; // Used to store a 16x240 image strip u_long bsStrip[16*240]; void init(void) { ResetGraph(0); // Initialize drawing engine with a complete reset (0) SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1] SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1] SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1] SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1] // Set video mode if (VMODE){ SetVideoMode(MODE_PAL);} SetDispMask(1); // Display on screen setRGB0(&draw[0], 155, 0, 150); // set color for first draw area setRGB0(&draw[1], 155, 0, 150); // set color for second draw area draw[0].isbg = 0; // set mask for draw areas. 1 means repainting the area with the RGB color each frame draw[1].isbg = 0; PutDispEnv(&disp[db]); // set the disp and draw environnments PutDrawEnv(&draw[db]); FntLoad(960, 0); // Load font to vram at 960,0(+128) FntOpen(MARGINX, MARGINY, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars } void display(void) { DrawSync(0); // Wait for all drawing to terminate VSync(0); // Wait for the next vertical blank PutDispEnv(&disp[db]); // set alternate disp and draw environnments PutDrawEnv(&draw[db]); db = !db; // flip db value (0 or 1) } int main(void) { // Init display init(); // Init CD system CdInit(); // Init heap InitHeap((u_long *)ramAddr, sizeof(ramAddr)); // If the other method was chosen at l.39 // InitHeap((void *)0x80030D40, 0x40000); // Set name of file to load loadFile = "\\BACE.BS;1"; // Get file position from filename CdSearchFile( &filePos, loadFile); // Allocate memory dataBuffer = malloc( BtoS(filePos.size) * CD_SECTOR_SIZE ); // Issue CdlSetloc CDROM command : Set the seek target position // Beware of a misnomed 'sector' member in the CdlLOC struct that should really be named 'frame'. // https://discord.com/channels/642647820683444236/663664210525290507/864912470996942910 CdControl(CdlSetloc, (u_char *)&filePos.pos, CtrlResult); // Read data and load it to dataBuffer CDreadOK = CdRead( (int)BtoS(filePos.size), (u_long *)dataBuffer, CdlModeSpeed ); // Wait for operation to complete CDreadResult = CdReadSync(0, 0); // Image Decompression // Initialize image processing subsystem DecDCTReset(0); // Find the needed buffer size bsBufferSize = DecDCTBufSize(dataBuffer); // Allocate buffer size at &bsWorkBuffer bsWorkBuffer = malloc( bsBufferSize ); // Decode Huffman (also called VLC: variable length coding ) compressed image DecDCTvlc( dataBuffer, (u_long *) bsWorkBuffer ); // Send decoded data to MDEC for RLE decoding. DecDCTin( (u_long*) bsWorkBuffer, 0); // Fetch decoded image in 16x240 strips for( bsDrawArea.x = 0; bsDrawArea.x < SCREENXRES; bsDrawArea.x += 16 ){ // Request decoded data from MDEC // Request 16 * 240 pixel high lines. // But size is in long words (4B), so divide by 2 to get words (2B) ? DecDCTout( bsStrip, (16*SCREENYRES)/2); // Wait for transfer to complete DecDCToutSync(0); // Load image data to fb LoadImage( &bsDrawArea, bsStrip ); } free( bsWorkBuffer ); while (1) // infinite loop { // Copy BS image to the other buffer MoveImage2(&disp[db].disp, 0, disp[!db].disp.y ); DrawSync(0); FntPrint("Hello BS! \n"); // Print filesize in bytes/sectors FntPrint("Bs Size: %dB sectors: %d\n", filePos.size, BtoS(filePos.size)); // Print heap and buffer addresses FntPrint("Heap: %x - Buf: %x\n", ramAddr, dataBuffer); FntPrint("bsWork: %x\nbsBufSize: %dB\n", bsWorkBuffer, bsBufferSize); FntFlush(-1); // Draw print stream display(); // Execute display() } return 0; } ================================================ FILE: hello_bs/isoconfig.xml ================================================ ================================================ FILE: hello_bs/system.cnf ================================================ BOOT=cdrom:\SCES_313.37;1 TCB=4 EVENT=10 STACK=801FFFF0 ================================================ FILE: hello_cd/Makefile ================================================ .PHONY: all cleansub all: mkpsxiso -y ./isoconfig.xml cleansub: $(MAKE) clean rm -f hello_cd.cue hello_cd.bin TARGET = hello_cd SRCS = hello_cd.c \ include ../common.mk ================================================ FILE: hello_cd/README.md ================================================ ## Loading a file from CD You need [mkpsxiso](https://github.com/Lameguy64/mkpsxiso) in your $PATH to generate a PSX disk image. ### Compile ```bash make all ``` ### Clean directory ```bash make cleansub ``` ### Adding data to the CD In `isoconfig.xml`, in the data track's `directory_tree` section, use this syntax : ```xml ``` See https://github.com/Lameguy64/mkpsxiso/blob/master/examples/example.xml for more details. ================================================ FILE: hello_cd/hello_cd.c ================================================ // Load files from CD and execute them // Schnappy 07-2021 #include #include #include #include #include #include // CD library #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 + (VMODE << 4) // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256 #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 0 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 7 // Text Field Height DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; short db = 0; // index of which buffer is used, values 0, 1 // CD specifics #define CD_SECTOR_SIZE 2048 // Converting bytes to sectors SECTOR_SIZE is defined in words, aka int #define BtoS(len) ( ( len + CD_SECTOR_SIZE - 1 ) / CD_SECTOR_SIZE ) // Name of file to load static char * loadFile; // libcd's CD file structure contains size, location and filename CdlFILE filePos = {0}; //~ struct EXEC * exeStruct; // Define start address of allocated memory // Let's use an array so we don't have to worry about using a memory segment that's already in use. static unsigned char ramAddr[0x40000]; // https://discord.com/channels/642647820683444236/663664210525290507/864936962199781387 // We could also set a memory address manually, but we have to make sure this won't get in the way of other routines. // void * ramAddr = (void *)0x80030D40; // Load data to this buffer u_long * dataBuffer; // Those are not strictly needed, but we'll use them to see the commands results. // They could be replaced by a 0 in the various functions they're used with. u_char CtrlResult[8]; // Value returned by CDread() - 1 is good, 0 is bad int CDreadOK = 0; // Value returned by CDsync() - Returns remaining sectors to load. 0 is good. int CDreadResult = 0; void init(void) { ResetGraph(0); // Initialize drawing engine with a complete reset (0) SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1] SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1] SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1] SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1] // Set video mode if (VMODE){ SetVideoMode(MODE_PAL);} SetDispMask(1); // Display on screen setRGB0(&draw[0], 255, 50, 50); // set color for first draw area setRGB0(&draw[1], 255, 50, 50); // set color for second draw area draw[0].isbg = 1; // set mask for draw areas. 1 means repainting the area with the RGB color each frame draw[1].isbg = 1; PutDispEnv(&disp[db]); // set the disp and draw environnments PutDrawEnv(&draw[db]); FntLoad(960, 0); // Load font to vram at 960,0(+128) FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars } void display(void) { DrawSync(0); // Wait for all drawing to terminate VSync(0); // Wait for the next vertical blank PutDispEnv(&disp[db]); // set alternate disp and draw environnments PutDrawEnv(&draw[db]); db = !db; // flip db value (0 or 1) } int main(void) { // Init display init(); // Init CD system CdInit(); // Init heap InitHeap((u_long *)ramAddr, sizeof(ramAddr)); // If the other method was chosen at l.39 // InitHeap((void *)0x80030D40, 0x40000); // Set name of file to load loadFile = "\\HELO.DAT;1"; // Get file position from filename CdSearchFile( &filePos, loadFile); // Allocate memory dataBuffer = malloc( BtoS(filePos.size) * CD_SECTOR_SIZE ); // Issue CdlSetloc CDROM command : Set the seek target position // Beware of a misnomed 'sector' member in the CdlLOC struct that should really be named 'frame'. // https://discord.com/channels/642647820683444236/663664210525290507/864912470996942910 CdControl(CdlSetloc, (u_char *)&filePos.pos, CtrlResult); // Read data and load it to dataBuffer CDreadOK = CdRead( (int)BtoS(filePos.size), (u_long *)dataBuffer, CdlModeSpeed ); // Wait for operation to complete CDreadResult = CdReadSync(0, 0); while (1) // infinite loop { // Print the content of the loaded file - See HELO.DAT FntPrint("%s%d\n", (char *)dataBuffer, VSync(-1)); // Print heap and buffer addresses FntPrint("Heap: %x - Buf: %x\n", ramAddr, dataBuffer); // Print returned values FntPrint("CdCtrl: %d\nRead : %d %d\n", CtrlResult[0], CDreadOK, CDreadResult); // Print filesize in bytes/sectors FntPrint("Size: %dB sectors: %d", filePos.size, BtoS(filePos.size)); FntFlush(-1); // Draw print stream display(); // Execute display() } return 0; } ================================================ FILE: hello_cd/isoconfig.xml ================================================ ================================================ FILE: hello_cd/system.cnf ================================================ BOOT=cdrom:\SCES_313.37;1 TCB=4 EVENT=10 STACK=801FFFF0 ================================================ FILE: hello_cdda/Makefile ================================================ .PHONY: all cleansub all: mkpsxiso -y ./isoconfig.xml cleansub: $(MAKE) clean rm -f hello_cdda.cue hello_cdda.bin TARGET = hello_cdda SRCS = hello_cdda.c \ include ../common.mk ================================================ FILE: hello_cdda/README.md ================================================ ## Compiling You need [mkpsxiso](https://github.com/Lameguy64/mkpsxiso) in your $PATH to generate a PSX disk image. Typing ```bash make ``` in a terminal will compile and generate the bin/cue files. Typing ```bash make cleansub ``` will clean the current directory ## Creating the disk image ```bash mkpsxiso -y isoconfig.xml ``` ## Using ffmpeg to generate a CDDA compliant Wav file Needed Specification : `RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, stereo 44100 Hz` ### Conversion ```bash ffmpeg -i input.mp3 -acodec pcm_s16le -ac 2 -ar 44100 output.wav ``` ### Merging two mono audio channels into one stereo channel ```bash ffmpeg -i herb.wav.new -filter_complex "[0:a][0:a]amerge=inputs=2[a]" -map "[a]" herbi.wav ``` ### Adding the audio track to the CD Add a track section **after** your data track in `isoconfig.xml` : ```xml ``` ## Music credits Track 1 : Beach Party by Kevin MacLeod Link: https://incompetech.filmmusic.io/song/3429-beach-party License: https://filmmusic.io/standard-license Track 2: Funk Game Loop by Kevin MacLeod Link: https://incompetech.filmmusic.io/song/3787-funk-game-loop License: https://filmmusic.io/standard-license ================================================ FILE: hello_cdda/hello_cdda.c ================================================ // CDDA track playback example // Schnappy 07-2021 #include #include #include #include #include #include // CD library #include // SPU library #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 + (VMODE << 4) // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256 #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 0 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 7 // Text Field Height DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; short db = 0; // index of which buffer is used, values 0, 1 // SPU attributes SpuCommonAttr spuSettings; // CD tracks int playing = -1; int tracks[] = {2, 0}; // Track to play , 1 is data, 2 is beach.wav, 3 is funk.wav. See isoconfig.xml void init(void) { ResetGraph(0); // Initialize drawing engine with a complete reset (0) SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1] SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1] SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1] SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1] // Set video mode if (VMODE){ SetVideoMode(MODE_PAL);} SetDispMask(1); // Display on screen setRGB0(&draw[0], 50, 50, 50); // set color for first draw area setRGB0(&draw[1], 50, 50, 50); // set color for second draw area draw[0].isbg = 1; // set mask for draw areas. 1 means repainting the area with the RGB color each frame draw[1].isbg = 1; PutDispEnv(&disp[db]); // set the disp and draw environnments PutDrawEnv(&draw[db]); FntLoad(960, 0); // Load font to vram at 960,0(+128) FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars } void display(void) { DrawSync(0); // Wait for all drawing to terminate VSync(0); // Wait for the next vertical blank PutDispEnv(&disp[db]); // set alternate disp and draw environnments PutDrawEnv(&draw[db]); db = !db; // flip db value (0 or 1) } int main(void) { int count = 0; int flip = 1; CdlLOC loc[100]; int ntoc; // Init display init(); // Init extended CD system CdInit(); // Init Spu SpuInit(); // Set master & CD volume to max spuSettings.mask = (SPU_COMMON_MVOLL | SPU_COMMON_MVOLR | SPU_COMMON_CDVOLL | SPU_COMMON_CDVOLR | SPU_COMMON_CDMIX); // Master volume should be in range 0x0000 - 0x3fff spuSettings.mvol.left = 0x3fff; spuSettings.mvol.right = 0x3fff; // Cd volume should be in range 0x0000 - 0x7fff spuSettings.cd.volume.left = 0x7fff; spuSettings.cd.volume.right = 0x7fff; // Enable CD input ON spuSettings.cd.mix = SPU_ON; // Apply settings SpuSetCommonAttr(&spuSettings); // Set transfer mode SpuSetTransferMode(SPU_TRANSFER_BY_DMA); // CD Playback setup // Play second audio track // Get CD TOC while ((ntoc = CdGetToc(loc)) == 0) { /* Read TOC */ FntPrint("No TOC found: please use CD-DA disc...\n"); } // Prevent out of bound pos for (int i = 1; i < ntoc; i++) { CdIntToPos(CdPosToInt(&loc[i]) - 74, &loc[i]); } // Those array will hold the return values of the CD commands u_char param[4], result[8]; // Set CD parameters ; Report Mode ON, CD-DA ON. See LibeOver47.pdf, p.188 param[0] = CdlModeRept|CdlModeDA; CdControlB (CdlSetmode, param, 0); /* set mode */ VSync (3); /* wait three vsync times */ // Play second track in toc array CdControlB (CdlPlay, (u_char *)&loc[3], 0); /* play */ while (1) // infinite loop { count ++; // Get current track number ~ every second // See LibeOver47.pdf, p.188 if (count%50 == 0){ CdReady(1, &result[0]); // current track number can also be obtained with // CdControlB (CdlGetlocP, 0, &result[0]); } // Switch track after ~ 20 seconds if (count%(50*20) == 0){ // Flip can have a value of 1 or -1 flip *= -1; uint8_t nextTrackIndex = result[1] + flip; // Send CD command to switch track CdControlB (CdlPlay, (u_char *)&loc[ nextTrackIndex ], 0); } FntPrint("Hello CDDA !\n"); // Send string to print stream FntPrint("Playback status: %d", result[1]); // Send string to print stream FntFlush(-1); // Draw printe stream display(); // Execute display() } return 0; } ================================================ FILE: hello_cdda/isoconfig.xml ================================================ ================================================ FILE: hello_cdda/system.cnf ================================================ BOOT=cdrom:\SCES_313.37;1 TCB=4 EVENT=10 STACK=801FFFF0 ================================================ FILE: hello_cube/Makefile ================================================ TARGET = hello_cube SRCS = hello_cube.c \ include ../common.mk ================================================ FILE: hello_cube/cube.c ================================================ SVECTOR modelCube_mesh[] = { { -128,128,128 }, { 128,128,128 }, { 128,128,-128 }, { -128,128,-128 }, { -128,-128,128 }, { 128,-128,128 }, { 128,-128,-128 }, { -128,-128,-128 } }; SVECTOR modelCube_normal[] = { 2365,-2365,-2365, 0, -2365,-2365,-2365, 0, -2365,-2365,2365, 0, 2365,-2365,2365, 0, 2365,2365,-2365, 0, -2365,2365,-2365, 0, -2365,2365,2365, 0, 2365,2365,2365, 0 }; CVECTOR modelCube_color[] = { 255,237,0, 0, 255,235,0, 0, 255,236,0, 0, 255,2,0, 0, 254,3,0, 0, 255,8,0, 0, 229,0,255, 0, 229,0,255, 0, 229,0,255, 0, 5,16,250, 0, 0,12,255, 0, 0,12,255, 0, 4,251,25, 0, 0,255,26, 0, 0,255,26, 0, 0,248,255, 0, 0,248,255, 0, 0,248,255, 0, 255,237,0, 0, 255,237,0, 0, 255,235,0, 0, 255,2,0, 0, 255,6,2, 0, 254,3,0, 0, 229,0,255, 0, 232,21,232, 0, 229,0,255, 0, 5,16,250, 0, 2,13,253, 0, 0,12,255, 0, 4,251,25, 0, 0,255,26, 0, 0,255,26, 0, 0,248,255, 0, 0,248,255, 0, 0,248,255, 0 }; int modelCube_index[] = { 0,2,3, 7,5,4, 4,1,0, 5,2,1, 2,7,3, 0,7,4, 0,1,2, 7,6,5, 4,5,1, 5,6,2, 2,6,7, 0,3,7 }; TMESH modelCube = { modelCube_mesh, modelCube_normal, 0, modelCube_color, 12 }; SVECTOR modelCube1_mesh[] = { { -128,128,128 }, { 128,128,128 }, { 128,128,-128 }, { -128,128,-128 }, { -128,-128,128 }, { 128,-128,128 }, { 128,-128,-128 }, { -128,-128,-128 } }; SVECTOR modelCube1_normal[] = { 2365,-2365,-2365, 0, -2365,-2365,-2365, 0, -2365,-2365,2365, 0, 2365,-2365,2365, 0, 2365,2365,-2365, 0, -2365,2365,-2365, 0, -2365,2365,2365, 0, 2365,2365,2365, 0 }; CVECTOR modelCube1_color[] = { 255,237,0, 0, 255,235,0, 0, 255,236,0, 0, 255,2,0, 0, 254,3,0, 0, 255,8,0, 0, 229,0,255, 0, 229,0,255, 0, 229,0,255, 0, 5,16,250, 0, 0,12,255, 0, 0,12,255, 0, 4,251,25, 0, 0,255,26, 0, 0,255,26, 0, 0,248,255, 0, 0,248,255, 0, 0,248,255, 0, 255,237,0, 0, 255,237,0, 0, 255,235,0, 0, 255,2,0, 0, 255,6,2, 0, 254,3,0, 0, 229,0,255, 0, 232,21,232, 0, 229,0,255, 0, 5,16,250, 0, 2,13,253, 0, 0,12,255, 0, 4,251,25, 0, 0,255,26, 0, 0,255,26, 0, 0,248,255, 0, 0,248,255, 0, 0,248,255, 0 }; int modelCube1_index[] = { 0,2,3, 7,5,4, 4,1,0, 5,2,1, 2,7,3, 0,7,4, 0,1,2, 7,6,5, 4,5,1, 5,6,2, 2,6,7, 0,3,7 }; TMESH modelCube1 = { modelCube1_mesh, modelCube1_normal, 0, modelCube1_color, 12 }; ================================================ FILE: hello_cube/hello_cube.c ================================================ /* primdrawG.c, by Schnappy, 12-2020 - Draw a gouraud shaded mesh exported as a TMESH by the blender <= 2.79b plugin io_export_psx_tmesh.py based on primdraw.c by Lameguy64 (http://www.psxdev.net/forum/viewtopic.php?f=64&t=537) 2014 Meido-Tek Productions. Controls: Start - Toggle interactive/non-interactive mode. Select - Reset object's position and angles. L1/L2 - Move object closer/farther. L2/R2 - Rotate object (XY). Up/Down/Left/Right - Rotate object (XZ/YZ). Triangle/Cross/Square/Circle - Move object up/down/left/right. */ #include #include #include #include #include // Sample vector model #include "cube.c" #define VMODE 0 #define SCREENXRES 320 #define SCREENYRES 240 #define CENTERX SCREENXRES/2 #define CENTERY SCREENYRES/2 #define OTLEN 2048 // Maximum number of OT entries #define PRIMBUFFLEN 32768 // Maximum number of POLY_GT3 primitives // Display and draw environments, double buffered DISPENV disp[2]; DRAWENV draw[2]; u_long ot[2][OTLEN]; // Ordering table (contains addresses to primitives) char primbuff[2][PRIMBUFFLEN]; // Primitive list // That's our prim buffer char * nextpri = primbuff[0]; // Primitive counter short db = 0; // Current buffer counter // Prototypes void init(void); void display(void); //~ void LoadTexture(u_long * tim, TIM_IMAGE * tparam); void init(){ // Reset the GPU before doing anything and the controller PadInit(0); ResetGraph(0); // Initialize and setup the GTE InitGeom(); SetGeomOffset(CENTERX, CENTERY); // x, y offset SetGeomScreen(CENTERX); // Distance between eye and screen // Set the display and draw environments SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], 0, 128, 255); setRGB0(&draw[1], 0, 128, 255); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); // Init font system FntLoad(960, 0); FntOpen(16, 16, 196, 64, 0, 256); } void display(void){ DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } int main() { int i; int PadStatus; int TPressed=0; int AutoRotate=1; long t, p, OTz, Flag; // t == vertex count, p == depth cueing interpolation value, OTz == value to create Z-ordered OT, Flag == see LibOver47.pdf, p.143 POLY_G3 *poly = {0}; // pointer to a POLY_G4 SVECTOR Rotate={ 232, 232, 0, 0 }; // Rotation coordinates VECTOR Trans={ 0, 0, CENTERX * 3, 0 }; // Translation coordinates // Scaling coordinates VECTOR Scale={ ONE/2, ONE/2, ONE/2, 0 }; // ONE == 4096 MATRIX Matrix={0}; // Matrix data for the GTE init(); // Main loop while (1) { // Read pad status PadStatus = PadRead(0); if (AutoRotate == 0) { if (PadStatus & PADL1) Trans.vz -= 4; if (PadStatus & PADR1) Trans.vz += 4; if (PadStatus & PADL2) Rotate.vz -= 8; if (PadStatus & PADR2) Rotate.vz += 8; if (PadStatus & PADLup) Rotate.vx -= 8; if (PadStatus & PADLdown) Rotate.vx += 8; if (PadStatus & PADLleft) Rotate.vy -= 8; if (PadStatus & PADLright) Rotate.vy += 8; if (PadStatus & PADRup) Trans.vy -= 2; if (PadStatus & PADRdown) Trans.vy += 2; if (PadStatus & PADRleft) Trans.vx -= 2; if (PadStatus & PADRright) Trans.vx += 2; if (PadStatus & PADselect) { Rotate.vx = Rotate.vy = Rotate.vz = 0; Scale.vx = Scale.vy = Scale.vz = ONE/2; Trans.vx = Trans.vy = 0; Trans.vz = CENTERX * 3; } } if (PadStatus & PADstart) { if (TPressed == 0) { AutoRotate = (AutoRotate + 1) & 1; Rotate.vx = Rotate.vy = Rotate.vz = 0; Scale.vx = Scale.vy = Scale.vz = ONE/2; Trans.vx = Trans.vy = 0; Trans.vz = CENTERX * 3; } TPressed = 1; } else { TPressed = 0; } if (AutoRotate) { Rotate.vy += 28; // Pan Rotate.vx += 28; // Tilt //~ Rotate.vz += 8; // Roll } // Clear the current OT ClearOTagR(ot[db], OTLEN); // Convert and set the matrixes RotMatrix(&Rotate, &Matrix); TransMatrix(&Matrix, &Trans); ScaleMatrix(&Matrix, &Scale); SetRotMatrix(&Matrix); SetTransMatrix(&Matrix); // Render the sample vector model t=0; // modelCube is a TMESH, len member == # vertices, but here it's # of triangle... So, for each tri * 3 vertices ... for (i = 0; i < (modelCube.len*3); i += 3) { poly = (POLY_G3 *)nextpri; // Initialize the primitive and set its color values SetPolyG3(poly); setRGB0(poly, modelCube.c[i].r , modelCube.c[i].g , modelCube.c[i].b); setRGB1(poly, modelCube.c[i+2].r, modelCube.c[i+2].g, modelCube.c[i+2].b); setRGB2(poly, modelCube.c[i+1].r, modelCube.c[i+1].g, modelCube.c[i+1].b); // Rotate, translate, and project the vectors and output the results into a primitive OTz = RotTransPers(&modelCube_mesh[modelCube_index[t]] , (long*)&poly->x0, &p, &Flag); OTz += RotTransPers(&modelCube_mesh[modelCube_index[t+2]], (long*)&poly->x1, &p, &Flag); OTz += RotTransPers(&modelCube_mesh[modelCube_index[t+1]], (long*)&poly->x2, &p, &Flag); // Sort the primitive into the OT OTz /= 3; if ((OTz > 0) && (OTz < OTLEN)) AddPrim(&ot[db][OTz-2], poly); nextpri += sizeof(POLY_G3); t+=3; } FntPrint("Hello gouraud shaded cube!\n"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_cubetex/Makefile ================================================ TARGET = hello_cubetex SRCS = hello_cubetex.c \ ../TIM/cubetex.tim \ include ../common.mk ================================================ FILE: hello_cubetex/cubetex.c ================================================ SVECTOR modelCube_mesh[] = { {32,32,-32.0}, {32,-32,-32}, {-32,-32,-32}, {-32,32,-32}, {32,32,32}, {32,-32,32}, {-32,-32,32}, {-32,32,32} }; SVECTOR modelCube_normal[] = { 0,-0,-1,0, 0,0,1,0, 1,0,-2,0, -9,-1,-3,0, -1,2,-1,0, 3,1,2,0, 0,0,-1,0, 0,-0,1,0, 1,-6,3,0, -5,-1,9,0, -1,2,-1,0, 2,1,2,0 }; SVECTOR modelCube_uv[] = { 84,84, 0, 0, 125,42, 0, 0, 84,42, 0, 0, 125,84, 0, 0, 84,125, 0, 0, 125,125, 0, 0, 1,84, 0, 0, 42,125, 0, 0, 42,84, 0, 0, 42,125, 0, 0, 84,84, 0, 0, 42,84, 0, 0, 42,1, 0, 0, 1,42, 0, 0, 42,42, 0, 0, 42,84, 0, 0, 1,42, 0, 0, 1,84, 0, 0, 84,84, 0, 0, 125,84, 0, 0, 125,42, 0, 0, 125,84, 0, 0, 84,84, 0, 0, 84,125, 0, 0, 1,84, 0, 0, 1,125, 0, 0, 42,125, 0, 0, 42,125, 0, 0, 84,125, 0, 0, 84,84, 0, 0, 42,1, 0, 0, 1,1, 0, 0, 1,42, 0, 0, 42,84, 0, 0, 42,42, 0, 0, 1,42, 0, 0 }; CVECTOR modelCube_color[] = { 255,255,255, 0, 255,255,255, 0, 255,0,251, 0, 255,255,255, 0, 255,5,7, 0, 255,255,255, 0, 255,255,255, 0, 255,255,255, 0, 4,18,255, 0, 255,5,7, 0, 255,255,255, 0, 255,255,255, 0, 254,255,23, 0, 122,255,107, 0, 255,255,255, 0, 255,255,255, 0, 255,255,255, 0, 254,255,94, 0, 255,255,255, 0, 35,255,11, 0, 255,255,255, 0, 255,255,255, 0, 255,255,255, 0, 255,5,7, 0, 255,255,255, 0, 255,5,7, 0, 255,255,255, 0, 255,5,7, 0, 255,255,255, 0, 255,255,255, 0, 254,255,23, 0, 255,255,255, 0, 122,255,107, 0, 255,255,255, 0, 54,65,255, 0, 255,255,255, 0 }; int modelCube_index[] = { 0,2,3, 7,5,4, 4,1,0, 5,2,1, 2,7,3, 0,7,4, 0,1,2, 7,6,5, 4,5,1, 5,6,2, 2,6,7, 0,3,7 }; TMESH modelCube = { modelCube_mesh, modelCube_normal, modelCube_uv, modelCube_color, 12 }; extern unsigned long _binary____TIM_cubetex_tim_start[]; extern unsigned long _binary____TIM_cubetex_tim_end[]; extern unsigned long _binary____TIM_cubetex_tim_length; TIM_IMAGE tim_cube; ================================================ FILE: hello_cubetex/hello_cubetex.c ================================================ /* primdrawG.c, by Schnappy, 12-2020 - Draw a gouraud shaded textured mesh exported as a TMESH by the blender <= 2.79b plugin io_export_psx_tmesh.py based on primdraw.c by Lameguy64 (http://www.psxdev.net/forum/viewtopic.php?f=64&t=537) 2014 Meido-Tek Productions. Demonstrates: - Using a primitive OT to draw triangles without libgs. - Using the GTE to rotate, translate, and project 3D primitives. Controls: Start - Toggle interactive/non-interactive mode. Select - Reset object's position and angles. L1/L2 - Move object closer/farther. L2/R2 - Rotate object (XY). Up/Down/Left/Right - Rotate object (XZ/YZ). Triangle/Cross/Square/Circle - Move object up/down/left/right. */ #include #include #include #include #include // Sample vector model #include "cubetex.c" #define VMODE 0 #define SCREENXRES 320 #define SCREENYRES 240 #define CENTERX SCREENXRES/2 #define CENTERY SCREENYRES/2 #define OTLEN 2048 // Maximum number of OT entries #define PRIMBUFFLEN 32768 // Maximum number of POLY_GT3 primitives // Display and draw environments, double buffered DISPENV disp[2]; DRAWENV draw[2]; u_long ot[2][OTLEN]; // Ordering table (contains addresses to primitives) char primbuff[2][PRIMBUFFLEN] = {0}; // Primitive list // That's our prim buffer //~ int primcnt=0; // Primitive counter char * nextpri = primbuff[0]; // Primitive counter short db = 0; // Current buffer counter // Prototypes void init(void); void display(void); void LoadTexture(u_long * tim, TIM_IMAGE * tparam); void init(){ // Reset the GPU before doing anything and the controller PadInit(0); ResetGraph(0); // Initialize and setup the GTE InitGeom(); SetGeomOffset(CENTERX, CENTERY); // x, y offset SetGeomScreen(CENTERX); // Distance between eye and screen // Set the display and draw environments SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); setRGB0(&draw[0], 0, 0, 255); setRGB0(&draw[1], 0, 0, 255); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); // Init font system FntLoad(960, 0); FntOpen(16, 16, 196, 64, 0, 256); } void display(void){ DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } void LoadTexture(u_long * tim, TIM_IMAGE * tparam){ // This part is from Lameguy64's tutorial series : lameguy64.net/svn/pstutorials/chapter1/3-textures.html login/pw: annoyingmous OpenTIM(tim); // Open the tim binary data, feed it the address of the data in memory ReadTIM(tparam); // This read the header of the TIM data and sets the corresponding members of the TIM_IMAGE structure LoadImage(tparam->prect, tparam->paddr); // Transfer the data from memory to VRAM at position prect.x, prect.y DrawSync(0); // Wait for the drawing to end if (tparam->mode & 0x8){ // check 4th bit // If 4th bit == 1, TIM has a CLUT LoadImage(tparam->crect, tparam->caddr); // Load it to VRAM at position crect.x, crect.y DrawSync(0); // Wait for drawing to end } } int main() { int i; int PadStatus; int TPressed=0; int AutoRotate=1; long t, p, OTz, Flag; // t == vertex count, p == depth cueing interpolation value, OTz == value to create Z-ordered OT, Flag == see LibOver47.pdf, p.143 POLY_GT3 *poly = {0}; // pointer to a POLY_G4 SVECTOR Rotate={ 0 }; // Rotation coordinates VECTOR Trans={ 0, 0, CENTERX, 0 }; // Translation coordinates // Scaling coordinates VECTOR Scale={ ONE, ONE, ONE, 0 }; // ONE == 4096 MATRIX Matrix={0}; // Matrix data for the GTE // Texture window DR_MODE * dr_mode; // Pointer to dr_mode prim RECT tws = {0, 0, 32, 32}; // Texture window coordinates : x, y, w, h init(); LoadTexture(_binary____TIM_cubetex_tim_start, &tim_cube); // Main loop while (1) { // Read pad status PadStatus = PadRead(0); if (AutoRotate == 0) { if (PadStatus & PADL1) Trans.vz -= 4; if (PadStatus & PADR1) Trans.vz += 4; if (PadStatus & PADL2) Rotate.vz -= 8; if (PadStatus & PADR2) Rotate.vz += 8; if (PadStatus & PADLup) Rotate.vx -= 8; if (PadStatus & PADLdown) Rotate.vx += 8; if (PadStatus & PADLleft) Rotate.vy -= 8; if (PadStatus & PADLright) Rotate.vy += 8; if (PadStatus & PADRup) Trans.vy -= 2; if (PadStatus & PADRdown) Trans.vy += 2; if (PadStatus & PADRleft) Trans.vx -= 2; if (PadStatus & PADRright) Trans.vx += 2; if (PadStatus & PADselect) { Rotate.vx = Rotate.vy = Rotate.vz = 0; Scale.vx = Scale.vy = Scale.vz = ONE; Trans.vx = Trans.vy = 0; Trans.vz = CENTERX; } } if (PadStatus & PADstart) { if (TPressed == 0) { AutoRotate = (AutoRotate + 1) & 1; Rotate.vx = Rotate.vy = Rotate.vz = 0; Scale.vx = Scale.vy = Scale.vz = ONE; Trans.vx = Trans.vy = 0; Trans.vz = CENTERX; } TPressed = 1; } else { TPressed = 0; } if (AutoRotate) { Rotate.vy += 8; // Pan Rotate.vx += 8; // Tilt //~ Rotate.vz += 8; // Roll } // Clear the current OT ClearOTagR(ot[db], OTLEN); // Convert and set the matrixes RotMatrix(&Rotate, &Matrix); TransMatrix(&Matrix, &Trans); ScaleMatrix(&Matrix, &Scale); SetRotMatrix(&Matrix); SetTransMatrix(&Matrix); // Render the sample vector model t=0; // modelCube is a TMESH, len member == # vertices, but here it's # of triangle... So, for each tri * 3 vertices ... for (i = 0; i < (modelCube.len*3); i += 3) { poly = (POLY_GT3 *)nextpri; // Initialize the primitive and set its color values SetPolyGT3(poly); ((POLY_GT3 *)poly)->tpage = getTPage(tim_cube.mode&0x3, 0, tim_cube.prect->x, tim_cube.prect->y ); setRGB0(poly, modelCube.c[i].r , modelCube.c[i].g , modelCube.c[i].b); setRGB1(poly, modelCube.c[i+2].r, modelCube.c[i+2].g, modelCube.c[i+2].b); setRGB2(poly, modelCube.c[i+1].r, modelCube.c[i+1].g, modelCube.c[i+1].b); setUV3(poly, modelCube.u[i].vx, modelCube.u[i].vy, modelCube.u[i+2].vx, modelCube.u[i+2].vy, modelCube.u[i+1].vx, modelCube.u[i+1].vy); // Rotate, translate, and project the vectors and output the results into a primitive OTz = RotTransPers(&modelCube_mesh[modelCube_index[t]] , (long*)&poly->x0, &p, &Flag); OTz += RotTransPers(&modelCube_mesh[modelCube_index[t+2]], (long*)&poly->x1, &p, &Flag); OTz += RotTransPers(&modelCube_mesh[modelCube_index[t+1]], (long*)&poly->x2, &p, &Flag); // Sort the primitive into the OT OTz /= 3; if ((OTz > 0) && (OTz < OTLEN)) AddPrim(&ot[db][OTz-2], poly); nextpri += sizeof(POLY_GT3); t+=3; } dr_mode = (DR_MODE *)nextpri; setDrawMode(dr_mode,1,0, getTPage(tim_cube.mode&0x3, 0, tim_cube.prect->x, tim_cube.prect->y), &tws); //set texture window AddPrim(&ot[db], dr_mode); nextpri += sizeof(DR_MODE); FntPrint("Hello textured cube!\n"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_cubetex_stp/Makefile ================================================ TARGET = hello_cubetex_stp SRCS = hello_cubetex_stp.c \ TIM/stpOnBlack.tim \ TIM/stpOnNonBlack.tim \ TIM/stpOn8bpp.tim \ include ../common.mk ================================================ FILE: hello_cubetex_stp/README.md ================================================ ![Hello_stp](https://wiki.arthus.net/assets/hello_cubetex_stp.png) # STP : Semi-Transparency usage This example shows the effect of activating Semi-Transparency on a textured primitive. Use the `SELECT` button to switch primitive semi-transparency on and off. Use the `START` button to cycle throught the 4 transparency rates. It also features a few C struct to facilitate access to the TIM file / pixel data. See [https://github.com/ABelliqueux/nolibgs_hello_worlds/tree/main/hello_poly_stp](https://github.com/ABelliqueux/nolibgs_hello_worlds/tree/main/hello_poly_stp) for more information on how to convert your images to TIM while preserving transparency. See [https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/TIM#transparency](https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/TIM#transparency) for mor details on transparency. ## Documentation http://psx.arthus.net/sdk/Psy-Q/DOCS/LibOver47.pdf, p.107 http://psx.arthus.net/sdk/Psy-Q/DOCS/FileFormat47.pdf, p.181 http://psx.arthus.net/sdk/Psy-Q/DOCS/LibRef47.pdf, p.306, 345, 365 ================================================ FILE: hello_cubetex_stp/cubetex.c ================================================ SVECTOR modelCube_mesh[] = { {48,48,-48.0}, {48,-48,-48}, {-48,-48,-48}, {-48,48,-48}, {48,48,48}, {48,-48,48}, {-48,-48,48}, {-48,48,48} }; SVECTOR modelCube_normal[] = { 0,-0,-1,0, 0,0,1,0, 1,0,-2,0, -9,-1,-3,0, -1,2,-1,0, 3,1,2,0, 0,0,-1,0, 0,-0,1,0, 1,-6,3,0, -5,-1,9,0, -1,2,-1,0, 2,1,2,0 }; SVECTOR modelCube_uv[] = { 84,84, 0, 0, 125,42, 0, 0, 84,42, 0, 0, 125,84, 0, 0, 84,125, 0, 0, 125,125, 0, 0, 1,84, 0, 0, 42,125, 0, 0, 42,84, 0, 0, 42,125, 0, 0, 84,84, 0, 0, 42,84, 0, 0, 42,1, 0, 0, 1,42, 0, 0, 42,42, 0, 0, 42,84, 0, 0, 1,42, 0, 0, 1,84, 0, 0, 84,84, 0, 0, 125,84, 0, 0, 125,42, 0, 0, 125,84, 0, 0, 84,84, 0, 0, 84,125, 0, 0, 1,84, 0, 0, 1,125, 0, 0, 42,125, 0, 0, 42,125, 0, 0, 84,125, 0, 0, 84,84, 0, 0, 42,1, 0, 0, 1,1, 0, 0, 1,42, 0, 0, 42,84, 0, 0, 42,42, 0, 0, 1,42, 0, 0 }; CVECTOR modelCube_color[] = { 255,255,255, 0, 255,255,255, 0, 255,0,251, 0, 255,255,255, 0, 255,5,7, 0, 255,255,255, 0, 255,255,255, 0, 255,255,255, 0, 4,18,255, 0, 255,5,7, 0, 255,255,255, 0, 255,255,255, 0, 254,255,23, 0, 122,255,107, 0, 255,255,255, 0, 255,255,255, 0, 255,255,255, 0, 254,255,94, 0, 255,255,255, 0, 35,255,11, 0, 255,255,255, 0, 255,255,255, 0, 255,255,255, 0, 255,5,7, 0, 255,255,255, 0, 255,5,7, 0, 255,255,255, 0, 255,5,7, 0, 255,255,255, 0, 255,255,255, 0, 254,255,23, 0, 255,255,255, 0, 122,255,107, 0, 255,255,255, 0, 54,65,255, 0, 255,255,255, 0 }; int modelCube_index[] = { 0,2,3, 7,5,4, 4,1,0, 5,2,1, 2,7,3, 0,7,4, 0,1,2, 7,6,5, 4,5,1, 5,6,2, 2,6,7, 0,3,7 }; TMESH modelCube = { modelCube_mesh, modelCube_normal, modelCube_uv, modelCube_color, 12 }; typedef struct RGB_PIX { u_int R:5, G:5, B:5, STP:1; } RGB_PIX; // Some structures to handle TIM files typedef struct PIXEL { u_long bnum; u_short DX, DY; u_short W, H; RGB_PIX data[]; } PIXEL; typedef struct CLUT { u_long bnum; u_short DX, DY; u_short W, H; u_short clut[]; } CLUT; typedef struct TIM_FILE_CLUT{ u_long ID; u_long flag; u_long clut; PIXEL pixel[]; } TIM_FILE_CLUT; typedef struct TIM_FILE{ u_long ID; u_long flag; PIXEL pixel[]; } TIM_FILE; extern TIM_FILE _binary_TIM_stpOnBlack_tim_start; extern TIM_FILE _binary_TIM_stpOnNonBlack_tim_start; extern TIM_FILE _binary_TIM_stpOn8bpp_tim_start; ================================================ FILE: hello_cubetex_stp/hello_cubetex_stp.c ================================================ // Demo the different settings and rates for pixel and primitive semi-transparency on a cube // Controls : // SELECT : Switch semi-transparency on/off on primitives // START : Cycle semi-transparency rates on/off on primitives // Schnappy 07-2021 #include #include #include #include #include //~ #include // Sample vector model #include "cubetex.c" #define VMODE 0 // Number of primitives to draw #define NUM_PRIM 3 #define SCREENXRES 320 #define SCREENYRES 240 #define CENTERX SCREENXRES/2 #define CENTERY SCREENYRES/2 #define MARGINX 16 // margins for text display #define MARGINY 16 #define OTLEN 2048 // Maximum number of OT entries #define PRIMBUFFLEN 32768 // Maximum number of POLY_GT3 primitives // Display and draw environments, double buffered DISPENV disp[2]; DRAWENV draw[2]; u_long ot[2][OTLEN]; // Ordering table (contains addresses to primitives) char primbuff[2][PRIMBUFFLEN]; // Primitive list // That's our prim buffer char * nextpri = primbuff[0]; // Primitive counter short db = 0; // Current buffer counter // Store TIM files in an array so we can iterate over them - see 'cubetex.c' for TIM_FILE struct and declaration TIM_FILE * timFiles[3]; TIM_IMAGE timImages[3]; // Prototypes void init(void); void display(void); void LoadTexture(TIM_FILE * tim, TIM_IMAGE * tparam); void init(){ // Reset the GPU before doing anything and the controller PadInit(0); ResetGraph(0); // Initialize and setup the GTE InitGeom(); SetGeomOffset(CENTERX, CENTERY); // x, y offset SetGeomScreen(CENTERX); // Distance between eye and screen // Set the display and draw environments SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); setRGB0(&draw[0], 0, 0, 255); setRGB0(&draw[1], 0, 0, 255); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); // Init font system FntLoad(960, 0); FntOpen(MARGINX, MARGINY, SCREENXRES - MARGINX * 2, SCREENXRES - MARGINY * 2, 0, 512 ); } void display(void){ DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } void LoadTexture(TIM_FILE * tim, TIM_IMAGE * tparam){ // This part is from Lameguy64's tutorial series : lameguy64.net/svn/pstutorials/chapter1/3-textures.html login/pw: annoyingmous OpenTIM((u_long*)tim); // Open the tim binary data, feed it the address of the data in memory ReadTIM(tparam); // This read the header of the TIM data and sets the corresponding members of the TIM_IMAGE structure LoadImage(tparam->prect, tparam->paddr); // Transfer the data from memory to VRAM at position prect.x, prect.y DrawSync(0); // Wait for the drawing to end if (tparam->mode & 0x8){ // check 4th bit // If 4th bit == 1, TIM has a CLUT LoadImage(tparam->crect, tparam->caddr); // Load it to VRAM at position crect.x, crect.y DrawSync(0); // Wait for drawing to end } } int main() { // Populate array with pointers to TIM data timFiles[0] = &_binary_TIM_stpOnBlack_tim_start; timFiles[1] = &_binary_TIM_stpOnNonBlack_tim_start; timFiles[2] = &_binary_TIM_stpOn8bpp_tim_start; // Pad values int pad, oldPad; // Set semi-transparency on (1) and off (0) int stpFlag = 0; // Set primitive semi-transparency rate - See LibOver47.pdf, p.107 int stpRate = 0; // If set, rotate cube int rotateCube = 1; // Array of pointers to a POLY_G4 we iterate over POLY_GT3 * poly[NUM_PRIM]; // Rotation vector SVECTOR rotVector={ 496, 0, 0, 0 }; // Translation vector VECTOR transVector= { -SCREENXRES/NUM_PRIM, -SCREENYRES/NUM_PRIM, SCREENXRES, 0}; // Init Disp/Drawenv, Font, etc. init(); // Load textures to VRAM for (char tim = 0; tim < NUM_PRIM; tim++){ LoadTexture(timFiles[tim], &timImages[tim]); } // Main loop while (1) { // Work matrix MATRIX Work= {0} ; // Triangle counters array - one for each cube long curTriangle[3] = {0,0,0}; // Clear the current OT ClearOTagR(ot[db], OTLEN); // Rotate cube if(rotateCube) rotVector.vy += 10; // Apply Transl, Rot, then matrix RotMatrix(&rotVector, &Work); TransMatrix(&Work, &transVector); SetRotMatrix(&Work); SetTransMatrix(&Work); // Draw NUM_PRIM primitives for (int prim = 0; prim < NUM_PRIM; prim++){ // Draw prims with an offset based on iteration number // A bit messy but the cubes are drawn where we want them :) transVector.vx = ( SCREENXRES/NUM_PRIM + ( prim * (SCREENXRES/NUM_PRIM + 48) )) - SCREENXRES + 56 ; transVector.vy = SCREENYRES/NUM_PRIM - SCREENYRES + 86; // Add vertical offset to second cube if ( prim == 1) { transVector.vy = SCREENYRES/NUM_PRIM + 48; } // Apply transl matrix TransMatrix(&Work, &transVector); SetTransMatrix(&Work); // modelCube is a TMESH, len member == # vertices, but here it's # of triangle... So, for each tri * 3 vertices ... for (int i = 0; i < (modelCube.len * 3); i += 3) { // t == vertex count, p == depth cueing interpolation value, OTz == value to create Z-ordered OT, Flag == see LibOver47.pdf, p.143 long p, OTz, Flag; // cast nextpri as POLY_GT3 poly[prim] = (POLY_GT3 *)nextpri; // Initialize the primitive and set its color values SetPolyGT3(poly[prim]); // Get TPAGE poly[prim]->tpage = getTPage( timImages[prim].mode&0x3, stpRate, timImages[prim].prect->x, timImages[prim].prect->y ); // Set RGB colors for each vertex setRGB0(poly[prim] , modelCube.c[i].r , modelCube.c[i].g , modelCube.c[i].b); setRGB1(poly[prim], modelCube.c[i+2].r, modelCube.c[i+2].g, modelCube.c[i+2].b); setRGB2(poly[prim], modelCube.c[i+1].r, modelCube.c[i+1].g, modelCube.c[i+1].b); // If 8/4bpp, load CLUT to vram if ( (timImages[prim].mode & 0x3) < 2 ) { setClut( poly[prim], timImages[prim].crect->x, timImages[prim].crect->y ); } // Set stpFlag SetSemiTrans(poly[prim], stpFlag); // Set UV coordinates setUV3(poly[prim], modelCube.u[i].vx, modelCube.u[i].vy, modelCube.u[i+2].vx, modelCube.u[i+2].vy, modelCube.u[i+1].vx, modelCube.u[i+1].vy); // Rotate, translate, and project the vectors and output the results into a primitive // curTriangle, +1, +2 point to the vertices index of the triangle we're drawing. OTz = RotTransPers(&modelCube_mesh[ modelCube_index[ curTriangle[prim] ] ] , ( long * ) &poly[prim]->x0, &p, &Flag); OTz += RotTransPers(&modelCube_mesh[ modelCube_index[ curTriangle[prim] + 2] ], ( long*) &poly[prim]->x1, &p, &Flag); OTz += RotTransPers(&modelCube_mesh[ modelCube_index[ curTriangle[prim] + 1] ], ( long * ) &poly[prim]->x2, &p, &Flag); // Average OTz value for 3 vertices // OTz is 1/4 of screen to vertex length OTz /= 3; // If OTz is in range (not to close) if ((OTz > 0) && (OTz < OTLEN)) // Add to ordering table, at index OTz-2 AddPrim(&ot[ db ][ OTz-2 ], poly[prim]); // Increment next primitive address nextpri += sizeof(POLY_GT3); // Increment to next triangle curTriangle[prim] += 3; } } // Get pad input pad = PadRead(0); // If select button is used if ( pad & PADselect && !(oldPad & PADselect) ){ // Flip STP flag stpFlag = !stpFlag; // Set flag to avoir misfire oldPad = pad; } // Reset flag when button released if ( !(pad & PADselect) && oldPad & PADselect) { oldPad = pad; } // If start button is used if ( pad & PADstart && !( oldPad & PADstart ) ){ // Switch STP rates stpRate > 2 ? stpRate = 0 : stpRate++; // Set flag to avoir misfire oldPad = pad; } // Reset flag when button released if (!(pad & PADstart) && oldPad & PADstart) { oldPad = pad; } FntPrint("Hello semi-transparency !\nPrim STP (push Select) : %d\nSTP rate (push start): %d\n\n\n\n\n\n\n\n\n\n\n\n", stpFlag, stpRate); FntPrint(" stp on black stp on col index\n\n\n\n\n\n\n\n\n\n\n\n"); FntPrint(" stp on non-black"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_font/Makefile ================================================ TARGET = hello_font SRCS = hello_font.c \ fnt.tim \ include ../common.mk ================================================ FILE: hello_font/README.md ================================================ See the **wiki** for more details : [https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/FONT](https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/FONT) ================================================ FILE: hello_font/hello_font.c ================================================ // Change the debug font face and colors ! // Schnappy 2020 #include #include #include #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 // Screen height #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 96 // margins for text display #define MARGINY 64 #define FONTSIZE 8 * 7 // Text Field Height DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; short db = 0; // index of which buffer is used, values 0, 1 #define FONTX 960 #define FONTY 0 // Two color vectors R,G,B CVECTOR fntColor = { 255, 0, 0 }; CVECTOR fntColorBG = { 0, 0, 0 }; extern unsigned long _binary_fnt_tim_start[]; extern unsigned long _binary_fnt_tim_end[]; extern unsigned long _binary_fnt_tim_length; // Loading an image to vram. See https://github.com/ABelliqueux/nolibgs_hello_worlds/blob/main/hello_sprt/hello_sprt.c#L42 TIM_IMAGE fontface; void LoadTexture(u_long * tim, TIM_IMAGE * tparam){ OpenTIM(tim); ReadTIM(tparam); LoadImage(tparam->prect, tparam->paddr); DrawSync(0); if (tparam->mode & 0x8){ // check 4th bit LoadImage(tparam->crect, tparam->caddr); DrawSync(0); } } void FntColor(CVECTOR fgcol, CVECTOR bgcol ) { // The debug font clut is at tx, ty + 128 // tx = bg color // tx + 1 = fg color // We can override the color by drawing a rect at these coordinates // // Define 1 pixel at 960,128 (background color) and 1 pixel at 961, 128 (foreground color) RECT fg = { FONTX+1, FONTY + 128, 1, 1 }; RECT bg = { FONTX, FONTY + 128, 1, 1 }; // Set colors ClearImage(&fg, fgcol.r, fgcol.g, fgcol.b); ClearImage(&bg, bgcol.r, bgcol.g, bgcol.b); } void init(void) { ResetGraph(0); // Initialize drawing engine with a complete reset (0) SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1] SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1] SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1] SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1] if (VMODE) // PAL { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; // add offset : 240 + 8 + 8 = 256 disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], 50, 50, 50); // set color for first draw area setRGB0(&draw[1], 50, 50, 50); // set color for second draw area draw[0].isbg = 1; // set mask for draw areas. 1 means repainting the area with the RGB color each frame draw[1].isbg = 1; PutDispEnv(&disp[db]); // set the disp and draw environnments PutDrawEnv(&draw[db]); FntLoad(960, 0); // Load font to vram at 960,0(+128) FntOpen(MARGINX, MARGINY, SCREENXRES, SCREENXRES, 0, 280 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars } void display(void) { DrawSync(0); // Wait for all drawing to terminate VSync(0); // Wait for the next vertical blank PutDispEnv(&disp[db]); // set alternate disp and draw environnments PutDrawEnv(&draw[db]); db = !db; // flip db value (0 or 1) } int main(void) { u_int t = 0; u_short step = 10; u_char * channel, * prevChannel; channel = &fntColor.r; prevChannel = &fntColor.b; init(); // execute init() LoadTexture(_binary_fnt_tim_start, &fontface); FntColor(fntColor, fntColorBG); while (1) // infinite loop { t++; if (fntColor.r >= 254 - step ){channel = &fntColor.g; prevChannel = &fntColor.r;} if (fntColor.g >= 254 - step ){channel = &fntColor.b; prevChannel = &fntColor.g;} if (fntColor.b >= 254 - step ){channel = &fntColor.r; prevChannel = &fntColor.b;} *channel += step; if (*prevChannel){ *prevChannel -= step; } //~ fntColorBG.g = 255 - color ; FntColor(fntColor, fntColorBG); // 4lines, 16 glyphs on each line // Glyphs are 5x7 pixels wide in 16bpp, FntPrint("!\"#$%%&'()*+,-./\n"); FntPrint("0123456789:;<=^?\n"); FntPrint("@ABCDEFGHIJKLMNO\n"); FntPrint("PQRSTUVWXYZ[\\]>"); FntPrint("\n\nHELLO DEBUG FONT! %d", t); FntFlush(-1); // Draw print stream display(); // Execute display() } return 0; } ================================================ FILE: hello_fx/Makefile ================================================ TARGET = hello_fx SRCS = hello_fx.c \ TIM/cube.tim \ TIM/sky.tim \ TIM/bg.tim \ include ../common.mk ================================================ FILE: hello_fx/cubetex.c ================================================ SVECTOR modelCube_mesh[] = { {48,48,-48.0}, {48,-48,-48}, {-48,-48,-48}, {-48,48,-48}, {48,48,48}, {48,-48,48}, {-48,-48,48}, {-48,48,48} }; SVECTOR modelCube_normal[] = { 0,-0,-1,0, 0,0,1,0, 1,0,-2,0, -9,-1,-3,0, -1,2,-1,0, 3,1,2,0, 0,0,-1,0, 0,-0,1,0, 1,-6,3,0, -5,-1,9,0, -1,2,-1,0, 2,1,2,0 }; SVECTOR modelCube_uv[] = { 84,84, 0, 0, 125,42, 0, 0, 84,42, 0, 0, 125,84, 0, 0, 84,125, 0, 0, 125,125, 0, 0, 1,84, 0, 0, 42,125, 0, 0, 42,84, 0, 0, 42,125, 0, 0, 84,84, 0, 0, 42,84, 0, 0, 42,1, 0, 0, 1,42, 0, 0, 42,42, 0, 0, 42,84, 0, 0, 1,42, 0, 0, 1,84, 0, 0, 84,84, 0, 0, 125,84, 0, 0, 125,42, 0, 0, 125,84, 0, 0, 84,84, 0, 0, 84,125, 0, 0, 1,84, 0, 0, 1,125, 0, 0, 42,125, 0, 0, 42,125, 0, 0, 84,125, 0, 0, 84,84, 0, 0, 42,1, 0, 0, 1,1, 0, 0, 1,42, 0, 0, 42,84, 0, 0, 42,42, 0, 0, 1,42, 0, 0 }; CVECTOR modelCube_color[] = { 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0, 128,128,128, 0 }; int modelCube_index[] = { 0,2,3, 7,5,4, 4,1,0, 5,2,1, 2,7,3, 0,7,4, 0,1,2, 7,6,5, 4,5,1, 5,6,2, 2,6,7, 0,3,7 }; TMESH modelCube = { modelCube_mesh, modelCube_normal, modelCube_uv, modelCube_color, 12 }; typedef struct RGB_PIX { u_int R:5, G:5, B:5, STP:1; } RGB_PIX; // Some structures to handle TIM files typedef struct PIXEL { u_long bnum; u_short DX, DY; u_short W, H; RGB_PIX data[]; } PIXEL; typedef struct CLUT { u_long bnum; u_short DX, DY; u_short W, H; u_short clut[]; } CLUT; typedef struct TIM_FILE_CLUT{ u_long ID; u_long flag; u_long clut; PIXEL pixel[]; } TIM_FILE_CLUT; typedef struct TIM_FILE{ u_long ID; u_long flag; PIXEL pixel[]; } TIM_FILE; ================================================ FILE: hello_fx/hello_fx.c ================================================ // Controls : // SELECT : Switch semi-transparency on/off on primitives // START : Cycle semi-transparency rates on/off on primitives // LEFT/RIGHT: Move forward cube // X : Reset Cube position // Schnappy 11-2021 #include #include #include #include #include #include #include // Sample vector model #include "cubetex.c" #define VMODE 0 // Number of primitives to draw #define NUM_PRIM 2 // Number of textures to load #define NUM_TEX 3 #define SCREENXRES 320 #define SCREENYRES 240 #define CENTERX SCREENXRES/2 #define CENTERY SCREENYRES/2 #define MARGINX 16 // margins for text display #define MARGINY 16 #define OTLEN 2048 // Maximum number of OT entries #define PRIMBUFFLEN 32768 // Maximum number of POLY_GT3 primitives // Display and draw environments, double buffered DISPENV disp[2]; DRAWENV draw[2]; u_long ot[2][OTLEN]; // Ordering table (contains addresses to primitives) char primbuff[2][PRIMBUFFLEN]; // Primitive list // That's our prim buffer char * nextpri = primbuff[0]; // Primitive counter short db = 0; // Current buffer counter // Store TIM files in an array so we can iterate over them - see 'cubetex.c' for TIM_FILE struct and declaration TIM_FILE * timFiles[3]; TIM_IMAGE timImages[3]; // Get included tim files address extern TIM_FILE _binary_TIM_cube_tim_start; extern TIM_FILE _binary_TIM_sky_tim_start; extern TIM_FILE _binary_TIM_bg_tim_start; // Light CVECTOR BGc = {130, 200, 255, 0}; // Back color VECTOR BKc = {128, 128, 128, 0}; // Light rotation angle SVECTOR lgtang = {0, 0, 0}; // These will be used to store the light rotation matrix, cube rotation matrix, and composite light matrix. MATRIX rotlgt, rotcube, light; // Local Light Matrix : Direction and reach of each light source. MATRIX lgtmat = { // X Y Z 0, -ONE, 0, // Lightsource 1 : here, the light source is at the Bottom-Left of the screen, and points into the screen. 0, 0, 0, // Lightsource 2 0, 0, 0, // Lightsource 3 }; // Local Color Matrix MATRIX cmat = { // L1 L2 L3 4096, 0, 0, // R 4096, 0, 0, // G 4096, 0, 0 // B }; // Prototypes void init(void); void display(void); void LoadTexture(TIM_FILE * tim, TIM_IMAGE * tparam); void init(){ // Reset the GPU before doing anything and the controller PadInit(0); ResetGraph(0); // Initialize and setup the GTE InitGeom(); SetGeomOffset(CENTERX, CENTERY); // x, y offset SetGeomScreen(CENTERX); // Distance between eye and screen // Set the display and draw environments SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Set far color SetFarColor( BGc.r, BGc.g, BGc.b ); // Set Ambient color SetBackColor( BKc.vx, BKc.vy, BKc.vz ); // Set Color matrix SetColorMatrix(&cmat); // Set Fog settings SetFogNearFar( 128, 1024, CENTERX ); setRGB0(&draw[0], 0, 0, 0); setRGB0(&draw[1], 0, 0, 0); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); // Init font system FntLoad(960, 0); FntOpen(MARGINX, MARGINY, SCREENXRES - MARGINX * 2, SCREENXRES - MARGINY * 2, 0, 512 ); } void display(void){ DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } void LoadTexture(TIM_FILE * tim, TIM_IMAGE * tparam){ // This part is from Lameguy64's tutorial series : lameguy64.net/svn/pstutorials/chapter1/3-textures.html login/pw: annoyingmous OpenTIM((u_long*)tim); // Open the tim binary data, feed it the address of the data in memory ReadTIM(tparam); // This read the header of the TIM data and sets the corresponding members of the TIM_IMAGE structure LoadImage(tparam->prect, tparam->paddr); // Transfer the data from memory to VRAM at position prect.x, prect.y DrawSync(0); // Wait for the drawing to end if (tparam->mode & 0x8){ // check 4th bit // If 4th bit == 1, TIM has a CLUT LoadImage(tparam->crect, tparam->caddr); // Load it to VRAM at position crect.x, crect.y DrawSync(0); // Wait for drawing to end } } int main() { // Populate array with pointers to TIM data timFiles[0] = &_binary_TIM_cube_tim_start; timFiles[1] = &_binary_TIM_sky_tim_start; timFiles[2] = &_binary_TIM_bg_tim_start; // Pad values int pad, oldPad; // Set semi-transparency on (1) and off (0) int stpFlag = 1; // Set primitive semi-transparency rate - See LibOver47.pdf, p.107 int stpRate = 0; // If set, rotate cube int rotateCube = 1; int offsetCube = 0; // Array of pointers to a POLY_G4 we iterate over POLY_GT3 * poly[NUM_PRIM]; // Rotation vector SVECTOR rotVector={ 384, 0, 128, 0 }; // Translation vector VECTOR transVector= { 0, 0, 256, 0}; // BG sprt POLY_FT4 * bg; // Normalized UV coordinates for the X axis long normH = ((255 << 12) / SCREENXRES); // Init Disp/Drawenv, Font, etc. init(); // Load textures to VRAM for (char tex = 0; tex < NUM_TEX; tex++){ LoadTexture(timFiles[tex], &timImages[tex]); } // Main loop while (1) { // Work matrix MATRIX Work= {0} ; // Triangle counters array - one for each cube long curTriangle[3] = {0,0,0}; // Clear the current OT ClearOTagR(ot[db], OTLEN); // Draw BG bg = (POLY_FT4 * )nextpri; SetPolyFT4(bg); bg->tpage = getTPage( timImages[2].mode&0x3, 0, timImages[2].prect->x, timImages[2].prect->y ); if ( (timImages[2].mode & 0x3) < 2 ) { setClut( bg, timImages[2].crect->x, timImages[2].crect->y ); } setRGB0(bg, 127,127,127); setUV4(bg, 0, 0, SCREENYRES, 0, 0, SCREENYRES, SCREENYRES, SCREENYRES ); setXY4(bg, 0 , 0, SCREENXRES, 0, 0 , SCREENYRES, SCREENXRES, SCREENYRES); addPrim(ot[db][OTLEN-1], bg); nextpri += sizeof(POLY_FT4); // Rotate cube if(rotateCube) rotVector.vy += 10; // Find and apply light rotation matrix // Find rotmat from light angles RotMatrix_gte(&lgtang, &rotlgt); // Find rotmat from cube angles RotMatrix_gte(&rotVector, &rotcube); // RotMatrix cube * RotMatrix light MulMatrix0(&rotcube, &rotlgt, &rotlgt); // Light Matrix * RotMatrix light MulMatrix0(&lgtmat, &rotlgt, &light); // Set new light matrix SetLightMatrix(&light); // Apply Transl, Rot, then matrix RotMatrix(&rotVector, &Work); TransMatrix(&Work, &transVector); SetRotMatrix(&Work); SetTransMatrix(&Work); long p, OTz, Flag; // Draw NUM_PRIM primitives for (int i = 0; i < (modelCube.len * 3); i += 3) { // Set projection matrices transVector.vx = 0; TransMatrix(&Work, &transVector); SetRotMatrix(&Work); SetTransMatrix(&Work); // Cast nextpri as POLY_GT3 poly[0] = (POLY_GT3 *)nextpri; poly[1] = (POLY_GT3 *)nextpri+sizeof(POLY_GT3); // Initialize the primitives SetPolyGT3(poly[0]); SetPolyGT3(poly[1]); // Reflection Cube // This cube has its UVs mapped directly to VRAM coordinates // We're using the framebuffers as a texture (0,0 and 0,256) // Get 256x256 texture page that's at x0, y0 poly[1]->tpage = getTPage( 2, stpRate, 0, !(db) << 8 // Here, we're using db's value that can be either 0 or 1 to determine the texture page Y coordinate. ); // Set STP SetSemiTrans(poly[1], stpFlag); // Map coordinates from drawarea (320x240) to texture size (128x128) in fixed point math // x = x * (256 / 320) => ( x * ( 128 * 4096 ) / 320 ) / 4096 // y = y * (240 / 240) => ( y * ( 240 * 4096 ) / 240 ) / 4096 => y * 2184 >> 12 -> y setUV3( poly[1], (poly[1]->x0 * normH) >> 12, poly[1]->y0 - (!(db) << 4) , // We're using db's value again to add a 16 pixels offset to the Y's coordinates of the UVs (poly[1]->x1 * normH) >> 12, poly[1]->y1 - (!(db) << 4), // We have to do that because the buffer is 240 high, whereas our texture page is 256, hence 256 - 240 == 16 (poly[1]->x2 * normH) >> 12, poly[1]->y2 - (!(db) << 4) ); // Draw "container" cube // This cube has a texture with transparent areas. // STP bit is set on PNG's alpha channel : img2tim -usealpha -org 320 0 -o cube.tim cube.png poly[0]->tpage = getTPage( timImages[0].mode&0x3, stpRate, timImages[0].prect->x, timImages[0].prect->y ); // If 8/4bpp, load CLUT to vram if ( (timImages[0].mode & 0x3) < 2 ) { setClut( poly[0], timImages[0].crect->x, timImages[0].crect->y ); } // Set UV coordinates setUV3(poly[0], modelCube.u[i].vx, modelCube.u[i].vy, modelCube.u[i+2].vx, modelCube.u[i+2].vy, modelCube.u[i+1].vx, modelCube.u[i+1].vy ); // Rotate, translate, and project the vectors and output the results into a primitive // curTriangle, +1, +2 point to the vertices index of the triangle we're drawing. OTz = RotTransPers(&modelCube_mesh[ modelCube_index[ curTriangle[0] ] ] , ( long * ) &poly[1]->x0, &p, &Flag); OTz += RotTransPers(&modelCube_mesh[ modelCube_index[ curTriangle[0] + 2] ], ( long*) &poly[1]->x1, &p, &Flag); OTz += RotTransPers(&modelCube_mesh[ modelCube_index[ curTriangle[0] + 1] ], ( long * ) &poly[1]->x2, &p, &Flag); // Here we're only messing with the matrices so that the foreground cube can be moved independantly from the backgound one. // In real code, you don't want to do the same calculation twice ! transVector.vx = offsetCube; TransMatrix(&Work, &transVector); SetRotMatrix(&Work); SetTransMatrix(&Work); OTz = RotTransPers(&modelCube_mesh[ modelCube_index[ curTriangle[0] ] ] , ( long * ) &poly[0]->x0, &p, &Flag); OTz += RotTransPers(&modelCube_mesh[ modelCube_index[ curTriangle[0] + 2] ], ( long*) &poly[0]->x1, &p, &Flag); OTz += RotTransPers(&modelCube_mesh[ modelCube_index[ curTriangle[0] + 1] ], ( long * ) &poly[0]->x2, &p, &Flag); // The right way to do it is re-using the results from the first RotTransPer() batch // i.e commenting lines 273 to 280 and uncommenting lines 284 to 289 //~ poly[0]->x0 = poly[1]->x0; //~ poly[0]->y0 = poly[1]->y0; //~ poly[0]->x1 = poly[1]->x1; //~ poly[0]->y1 = poly[1]->y1; //~ poly[0]->x2 = poly[1]->x2; //~ poly[0]->y2 = poly[1]->y2; // Average OTz value for 3 vertices // OTz is 1/4 of screen to vertex length OTz /= 3; // Work color vectors // This is the hue of the transparent cube CVECTOR prismCol = {0xff,0xff,0x0,0x0}; // This will store the result of the depth cueing. CVECTOR outCol, outCol1, outCol2 = { 0,0,0,0 }; // Find local color from three normal vectors and perform depth cueing. gte_NormalColorDpq3( &modelCube.n[i+0], &modelCube.n[i+2], &modelCube.n[i+3], &prismCol, p, &outCol, &outCol1, &outCol2); // Set vertex colors on transparent/background cube setRGB0(poly[1], outCol.r, outCol.g , outCol.b); setRGB1(poly[1], outCol1.r, outCol1.g, outCol1.b); setRGB2(poly[1], outCol2.r, outCol2.g, outCol2.b); // Non-transparent/foreground cube color // Find local color from three normal vectors and perform depth cueing. gte_NormalColorDpq( &modelCube.n[i+0], &modelCube.c[i+0], p, &outCol); gte_NormalColorDpq( &modelCube.n[i+2], &modelCube.c[i+2], p, &outCol2); gte_NormalColorDpq( &modelCube.n[i+1], &modelCube.c[i+1], p, &outCol1); // Set vertex colors setRGB0(poly[0], outCol.r, outCol.g , outCol.b); setRGB1(poly[0], outCol1.r, outCol1.g, outCol1.b); setRGB2(poly[0], outCol2.r, outCol2.g, outCol2.b); // If OTz is in range (not too close) if ((OTz > 0) && (OTz < OTLEN)) // Add to ordering table, at index OTz-2 AddPrim(&ot[ db ][ OTz-2 ], poly[0]); AddPrim(&ot[ db ][ OTz-2 ], poly[1]); // Increment next primitive address nextpri += sizeof(POLY_GT3)*2; // Increment to next triangle curTriangle[0] += 3; curTriangle[1] += 3; } // Get pad input pad = PadRead(0); // If select button is used if ( pad & PADselect && !(oldPad & PADselect) ){ // Flip STP flag stpFlag = !stpFlag; // Set flag to avoir misfire oldPad = pad; } // Reset flag when button released if ( !(pad & PADselect) && oldPad & PADselect) { oldPad = pad; } // If start button is used if ( pad & PADstart && !( oldPad & PADstart ) ){ // Switch STP rates stpRate > 2 ? stpRate = 0 : stpRate++; // Set flag to avoir misfire oldPad = pad; } // Reset flag when button released if (!(pad & PADstart) && oldPad & PADstart) { oldPad = pad; } if ( pad & PADRdown && !( oldPad & PADRdown ) ){ // Switch STP rates offsetCube = 0; // Set flag to avoir misfire oldPad = pad; } // Reset flag when button released if (!(pad & PADRdown) && oldPad & PADRdown) { oldPad = pad; } if ( pad & PADLright && !( oldPad & PADLright ) ){ offsetCube += 6; } if ( pad & PADLleft && !( oldPad & PADLleft ) ){ offsetCube -= 6; } FntPrint("Hello fx !\n"); FntPrint("Select: STP on/off\nStart: Cycle STP rates\nLeft/Right: Move FG cube.\nX: Reset cube pos\n"); FntPrint("STP : %d\n", stpFlag); FntPrint("STP rate : %d\n", stpRate); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_gte_opti/Makefile ================================================ TARGET = hello_gte_opti SRCS = hello_gte_opti.c \ include ../common.mk ================================================ FILE: hello_gte_opti/hello_gte_opti.c ================================================ // Hello free cycles ! // // Ref : /psyq/DOCS/Devrefs/Inlinref.pdf, p.18 // /psyq/psx/sample/scea/GTE // https://psx-spx.consoledev.net/geometrytransformationenginegte/ // PSX / Z+ // screen / //coordinate +-----X+ //system / | // eye | Y+ // // Credits, thanks : Nicolas Noble, Sickle, Lameguy64 @ psxdev discord : https://discord.com/invite/N2mmwp // https://discord.com/channels/642647820683444236/663664210525290507/834831466100949002 // Schnappy 2021 #include #include #include #include #include // OldWorld PsyQ has a inline_c.h file for inline GTE functions. We have to use the one at https://github.com/grumpycoders/pcsx-redux/blob/07f9b02d1dbb68f57a9f5b9773041813c55a4913/src/mips/psyq/include/inline_n.h // because the real GTE commands are needed in nugget : https://psx-spx.consoledev.net/geometrytransformationenginegte/#gte-coordinate-calculation-commands #include // RAM -> CPU and CPU -> GTE macros : #include "../includes/CPUMAC.H" #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 + (VMODE << 4) // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256 #define CENTERX ( SCREENXRES >> 1 ) // Center of screen on x #define CENTERY ( SCREENYRES >> 1 ) // Center of screen on y #define MARGINX 0 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 7 // Text Field Height #define OTLEN 10 // Ordering Table Length DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768] = {1}; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] short db = 0; // index of which buffer is used, values 0, 1 // DCache setup #define dc_camdirp ((sshort*) getScratchAddr(0)) #define dc_ip ((uchar*) getScratchAddr(1)) #define dc_opzp ((slong*) getScratchAddr(2)) #define dc_wmatp ((MATRIX*) getScratchAddr(3)) #define dc_cmatp ((MATRIX*) getScratchAddr(9)) #define dc_sxytbl ((DVECTOR*) getScratchAddr(15)) void init(void) { ResetGraph(0); // Initialize and setup the GTE InitGeom(); //~ SetGeomOffset(CENTERX,CENTERY); gte_SetGeomOffset(CENTERX,CENTERY); gte_SetGeomScreen(CENTERX); // Set display environment SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw environment SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE){ SetVideoMode(MODE_PAL);} SetDispMask(1); // Set background color setRGB0(&draw[0], 50, 50, 50); setRGB0(&draw[1], 50, 50, 50); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { // Wait for drawing DrawSync(0); // Wait for vsync VSync(1); // Flip DISP and DRAW env PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); // Flip db index db = !db; // Get next primitive in buffer nextpri = primbuff[db]; } int main(void) { long p, flag, OTz; SVECTOR rotVector = {0}; SVECTOR rotVector4 = {0}; // Initialize rotation vector {x, y, z} - ALWAYS ! VECTOR transVector = {0, 0, CENTERX, 0}; // Initialize translation vector {x, y, z} SVECTOR vertPos[4] = { { 0, -32, 0, 0 }, // Vert 1 { 32, 0, 0, 0 }, // Vert 2 { -32, 0, 0, 0 }, { 0, 32, 0, 0 } }; // Vert 3 MATRIX workMatrix = {0}; POLY_F3 * poly = {0}; // pointer to a POLY_F4 POLY_F4 * poly4 = {0}; // pointer to a POLY_F4 init(); // Declare registers register ulong ur0 asm("$16"); register ulong ur1 asm("$17"); register ulong ur2 asm("$18"); register ulong ur3 asm("$19"); register ulong ur4 asm("$20"); register ulong ur5 asm("$21"); while (1) { // Set Ordering table ClearOTagR(ot[db], OTLEN); // Cast next primitives in buffer as a POLY_F3 and a POLY_F4 (see display() ) poly = (POLY_F3 *)nextpri; nextpri += sizeof(POLY_F3); poly4 = (POLY_F4 *)nextpri; // Set matrices - Move to left of screen // Draw on the left part of the screen transVector.vx = -CENTERX/2; // Increment rotation angle on Y axis rotVector.vy += 1; // Find rotation matrix from vector, store in RotMatrix_gte(&rotVector, &workMatrix); // Ditto for translation TransMatrix(&workMatrix, &transVector); // Set the matrices we just found gte_SetRotMatrix(&workMatrix); gte_SetTransMatrix(&workMatrix); // Draw a Tri and a Quad // Copy Tri vertices from ram to cpu registers casting as ulong so that ur0 (len 32bits) contains vx and vy (2 * 8bits) // Hence the use of vx, vz members cpu_ldr(ur0,(ulong*)&vertPos[0].vx); // Put vx, vy value in ur0 cpu_ldr(ur1,(ulong*)&vertPos[0].vz); // Put vz, pad value in ur1 cpu_ldr(ur2,(ulong*)&vertPos[1].vx); cpu_ldr(ur3,(ulong*)&vertPos[1].vz); cpu_ldr(ur4,(ulong*)&vertPos[2].vx); cpu_ldr(ur5,(ulong*)&vertPos[2].vz); // Load the gte registers from the cpu registers (gte-cpu move 1 cycle) - mtc2 %0, $0; cpu_gted0(ur0); cpu_gted1(ur1); cpu_gted2(ur2); cpu_gted3(ur3); cpu_gted4(ur4); cpu_gted5(ur5); // Tri RotTransPers3 // The two last cpu->gte copy will happen during the 2 nops in gte_rtpt() gte_rtpt(); // Fill the cpu registers with the Quad vertices cpu_ldr(ur0,(ulong*)&vertPos[0].vx); cpu_ldr(ur1,(ulong*)&vertPos[0].vz); cpu_ldr(ur2,(ulong*)&vertPos[1].vx); cpu_ldr(ur3,(ulong*)&vertPos[1].vz); cpu_ldr(ur4,(ulong*)&vertPos[2].vx); cpu_ldr(ur5,(ulong*)&vertPos[2].vz); // Get nclip value, and win two cycles gte_nclip(); // Copy Tri 's screen coordinates from gte registers to d-cache. gte_stsxy3c(&dc_sxytbl[0]); // Set matrices - Move to right of screen transVector.vx = CENTERX/2; // Increment rot on X/Y axis rotVector4.vy -= 1 ; rotVector4.vx -= 1 ; // Set matrices RotMatrix_gte(&rotVector4, &workMatrix); TransMatrix(&workMatrix, &transVector); gte_SetRotMatrix(&workMatrix); gte_SetTransMatrix(&workMatrix); // Load the gte registers from the cpu registers (gte-cpu move 1 cycle) - mtc2 %0, $0; cpu_gted0(ur0); cpu_gted1(ur1); cpu_gted2(ur2); cpu_gted3(ur3); cpu_gted4(ur4); cpu_gted5(ur5); // Quad RotTransPers3 // Getting 2 cycles back thanks to nops gte_rtpt(); // gte_nclip() has 2 nops, lets use them to load the remaining vertex data from ram->cpu register cpu_ldr(ur0,(ulong*)&vertPos[3].vx); cpu_ldr(ur1,(ulong*)&vertPos[3].vz); // Calculate nclip (outer product) gte_nclip(); // Copy result to d-cache + 3 gte_stsxy3c(&dc_sxytbl[3]); // Copy from cpu-gte cpu_gted0(ur0); cpu_gted1(ur1); // Quad last vertex RotTransPers // These two last cpu->gte load are free :p gte_rtps(); gte_nclip(); // Copy last vertex value to d-cache gte_stsxy(&dc_sxytbl[6]); // Get p, flag, OTz gte_stdp(&p); gte_stflg(&flag); gte_stszotz(&OTz); // That's 10 cycles we won back ? // Copy vertices data from d-cache to ram // Tri *(unsigned long long*)&poly->x0 = *(unsigned long long*)&dc_sxytbl[0]; *(ulong*)&poly->x2 = *(ulong*)&dc_sxytbl[2]; // Quad *(unsigned long long*)&poly4->x0 = *(unsigned long long*)&dc_sxytbl[3]; *(unsigned long long*)&poly4->x2 = *(unsigned long long*)&dc_sxytbl[5]; // Initialize polygons setPolyF3(poly); setRGB0(poly, 255, 0, 255); setPolyF4(poly4); setRGB0(poly4, 0, 255, 255); // Add to OT addPrim(ot[db], poly); addPrim(ot[db], poly4); // Display text FntPrint("Hello Free cycles !\n"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_light/Makefile ================================================ TARGET = hello_light SRCS = hello_light.c \ include ../common.mk ================================================ FILE: hello_light/cube.c ================================================ SVECTOR modelCube_mesh[] = { { -128,128,128 }, { 128,128,128 }, { 128,128,-128 }, { -128,128,-128 }, { -128,-128,128 }, { 128,-128,128 }, { 128,-128,-128 }, { -128,-128,-128 } }; SVECTOR modelCube_normal[] = { 2365,-2365,-2365, 0, -2365,-2365,-2365, 0, -2365,-2365,2365, 0, 2365,-2365,2365, 0, 2365,2365,-2365, 0, -2365,2365,-2365, 0, -2365,2365,2365, 0, 2365,2365,2365, 0 }; CVECTOR modelCube_color[] = { 255,237,0, 0, 255,235,0, 0, 255,236,0, 0, 255,2,0, 0, 254,3,0, 0, 255,8,0, 0, 229,0,255, 0, 229,0,255, 0, 229,0,255, 0, 5,16,250, 0, 0,12,255, 0, 0,12,255, 0, 4,251,25, 0, 0,255,26, 0, 0,255,26, 0, 0,248,255, 0, 0,248,255, 0, 0,248,255, 0, 255,237,0, 0, 255,237,0, 0, 255,235,0, 0, 255,2,0, 0, 255,6,2, 0, 254,3,0, 0, 229,0,255, 0, 232,21,232, 0, 229,0,255, 0, 5,16,250, 0, 2,13,253, 0, 0,12,255, 0, 4,251,25, 0, 0,255,26, 0, 0,255,26, 0, 0,248,255, 0, 0,248,255, 0, 0,248,255, 0 }; int modelCube_index[] = { 0,2,3, 7,5,4, 4,1,0, 5,2,1, 2,7,3, 0,7,4, 0,1,2, 7,6,5, 4,5,1, 5,6,2, 2,6,7, 0,3,7 }; TMESH modelCube = { modelCube_mesh, modelCube_normal, 0, modelCube_color, 12 }; SVECTOR modelCube1_mesh[] = { { -128,128,128 }, { 128,128,128 }, { 128,128,-128 }, { -128,128,-128 }, { -128,-128,128 }, { 128,-128,128 }, { 128,-128,-128 }, { -128,-128,-128 } }; SVECTOR modelCube1_normal[] = { 2365,-2365,-2365, 0, -2365,-2365,-2365, 0, -2365,-2365,2365, 0, 2365,-2365,2365, 0, 2365,2365,-2365, 0, -2365,2365,-2365, 0, -2365,2365,2365, 0, 2365,2365,2365, 0 }; CVECTOR modelCube1_color[] = { 255,237,0, 0, 255,235,0, 0, 255,236,0, 0, 255,2,0, 0, 254,3,0, 0, 255,8,0, 0, 229,0,255, 0, 229,0,255, 0, 229,0,255, 0, 5,16,250, 0, 0,12,255, 0, 0,12,255, 0, 4,251,25, 0, 0,255,26, 0, 0,255,26, 0, 0,248,255, 0, 0,248,255, 0, 0,248,255, 0, 255,237,0, 0, 255,237,0, 0, 255,235,0, 0, 255,2,0, 0, 255,6,2, 0, 254,3,0, 0, 229,0,255, 0, 232,21,232, 0, 229,0,255, 0, 5,16,250, 0, 2,13,253, 0, 0,12,255, 0, 4,251,25, 0, 0,255,26, 0, 0,255,26, 0, 0,248,255, 0, 0,248,255, 0, 0,248,255, 0 }; int modelCube1_index[] = { 0,2,3, 7,5,4, 4,1,0, 5,2,1, 2,7,3, 0,7,4, 0,1,2, 7,6,5, 4,5,1, 5,6,2, 2,6,7, 0,3,7 }; TMESH modelCube1 = { modelCube1_mesh, modelCube1_normal, 0, modelCube1_color, 12 }; ================================================ FILE: hello_light/hello_light.c ================================================ /* hello_light.c, by Schnappy, 06-2021 - Demonstrates setting and using light sources in 3D without libgs. Controls: Start - Toggle interactive/non-interactive mode. Select - Reset object's position and angles. L1/L2 - Move object closer/farther. L2/R2 - Rotate object (XY). Up/Down/Left/Right - Rotate object (XZ/YZ). Triangle/Cross/Square/Circle - Move object up/down/left/right. based on primdraw.c by Lameguy64 (http://www.psxdev.net/forum/viewtopic.php?f=64&t=537) 2014 Meido-Tek Productions. */ #include #include #include #include #include // Sample vector model #include "cube.c" #define VMODE 0 #define SCREENXRES 320 #define SCREENYRES 240 #define CENTERX SCREENXRES/2 #define CENTERY SCREENYRES/2 #define OTLEN 2048 // Maximum number of OT entries #define PRIMBUFFLEN 32768 // Maximum number of POLY_GT3 primitives // Display and draw environments, double buffered DISPENV disp[2]; DRAWENV draw[2]; u_long ot[2][OTLEN]; // Ordering table (contains addresses to primitives) char primbuff[2][PRIMBUFFLEN]; // Primitive list // That's our prim buffer char * nextpri = primbuff[0]; // Primitive counter short db = 0; // Current buffer counter long t, p, OTz, Flag; // t == vertex count, p == depth cueing interpolation value, OTz == value to create Z-ordered OT, Flag == see LibOver47.pdf, p.143 // Lighting // See PsyQ's LibOver47.pdf, p.133 for more details on the purpose of each component and full calculations. // Far color : This is the color used to fade to when the mesh is far from the cam (NearFog) CVECTOR BGc = {150, 50, 75, 0}; // Back color VECTOR BKc = {128, 128, 128, 0}; // Light rotation angle SVECTOR lgtang = {0, 0, 0}; // These will be used to store the light rotation matrix, cube rotation matrix, and composite light matrix. MATRIX rotlgt, rotcube, light; // Local Light Matrix : Direction and reach of each light source. // Each light points in the direction aligned with the axis, hence direction is in the same coordinate system as the PSX (see l.23-30 of this file) // Negative/positive value denotes light direction on corresponding axis // -4096 > Value < 4096 denotes reach/intensity of light source MATRIX lgtmat = { // X Y Z -ONE, -ONE, ONE, // Lightsource 1 : here, the light source is at the Bottom-Left of the screen, and points into the screen. 0, 0, 0, // Lightsource 2 0, 0, 0, // Lightsource 3 }; // Local Color Matrix // Set color of each light source (L) // Value range : 0 > x < 4096 MATRIX cmat = { // L1 L2 L3 4096, 0, 0, // R 4096, 0, 0, // G 4096, 0, 0 // B }; // Prototypes void init(void); void display(void); void init(){ // Reset the GPU before doing anything and the controller PadInit(0); ResetGraph(0); // Initialize and setup the GTE InitGeom(); SetGeomOffset(CENTERX, CENTERY); // x, y offset SetGeomScreen(CENTERX); // Distance between eye and screen // Set the display and draw environments SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Set light env // Set far color SetFarColor( BGc.r, BGc.g, BGc.b ); // Set Ambient color SetBackColor( BKc.vx, BKc.vy, BKc.vz ); // Set Color matrix SetColorMatrix(&cmat); // Set Fog settings SetFogNearFar( 1200, 2200, SCREENXRES ); setRGB0(&draw[0], 0, 0, 255); setRGB0(&draw[1], 0, 0, 255); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); // Init font system FntLoad(960, 0); FntOpen(16, 16, 196, 64, 0, 256); } void display(void){ DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } int main() { int i; int PadStatus; int TPressed=0; int AutoRotate=1; // Rotating cube POLY_G3 * poly; SVECTOR Rotate={ ONE/6,ONE/6,ONE/6 }; // Rotation coordinates VECTOR Trans={ -SCREENXRES/2, 0, CENTERX * 3, 0 }; // Translation coordinates VECTOR Scale={ ONE/2, ONE/2, ONE/2, 0 }; // Scaling coordinates : ONE == 4096 MATRIX Matrix={0}; // Matrix data for the GTE // Static cube POLY_G3 * poly1; // pointer to a POLY_G4 SVECTOR Rotate1={ ONE/6, ONE/6, ONE/6, 0 }; // Rotation coordinates VECTOR Trans1={ SCREENXRES/2, 0, CENTERX * 3, 0 }; // Translation coordinates VECTOR Scale1={ ONE/2, ONE/2, ONE/2, 0 }; // Scaling coordinates : ONE == 4096 MATRIX Matrix1={0}; // Matrix data for the GTE init(); // Main loop while (1) { // Read pad status PadStatus = PadRead(0); if (AutoRotate == 0) { if (PadStatus & PADL1) Trans.vz -= 4; if (PadStatus & PADR1) Trans.vz += 4; if (PadStatus & PADL2) Rotate.vz -= 8; if (PadStatus & PADR2) Rotate.vz += 8; if (PadStatus & PADLup) Rotate.vx -= 8; if (PadStatus & PADLdown) Rotate.vx += 8; if (PadStatus & PADLleft) Rotate.vy -= 8; if (PadStatus & PADLright) Rotate.vy += 8; if (PadStatus & PADRup) Trans.vy -= 2; if (PadStatus & PADRdown) Trans.vy += 2; if (PadStatus & PADRleft) Trans.vx -= 2; if (PadStatus & PADRright) Trans.vx += 2; } if (PadStatus & PADstart) { if (TPressed == 0) { AutoRotate = (AutoRotate + 1) & 1; Rotate.vy = Rotate.vx = Rotate.vz = ONE/6; Scale.vx = Scale.vy = Scale.vz = ONE/2; Trans.vx = -SCREENXRES/2; Trans.vy = 0; Trans.vz = CENTERX * 3; } TPressed = 1; } else { TPressed = 0; } if (AutoRotate) { Rotate.vy += 8; // Pan Rotate.vx += 8; // Tilt //~ Rotate.vz += 8; // Roll } // Clear the current OT ClearOTagR(ot[db], OTLEN); // Render the sample vector model t=0; // modelCube is a TMESH, len member == # vertices, but here it's # of triangle... So, for each tri * 3 vertices ... for (i = 0; i < (modelCube.len*3); i += 3) { poly = (POLY_G3 *)nextpri; // Initialize the primitive and set its color values SetPolyG3(poly); // Rotate, translate, and project the vectors and output the results into a primitive // Could be replaced with one call with RotTransPers3() OTz = RotTransPers(&modelCube_mesh[modelCube_index[t]] , (long*)&poly->x0, &p, &Flag); OTz += RotTransPers(&modelCube_mesh[modelCube_index[t+2]], (long*)&poly->x1, &p, &Flag); OTz += RotTransPers(&modelCube_mesh[modelCube_index[t+1]], (long*)&poly->x2, &p, &Flag); // Find light color // Work color vectors CVECTOR outCol, outCol1, outCol2 = { 0,0,0,0 }; // Find local color from three normal vectors and perform depth cueing. // Could be replaced with one call with NormalColorDpq3() NormalColorDpq(&modelCube.n[ modelCube_index[t+0] ], &modelCube.c[i+0], p, &outCol); NormalColorDpq(&modelCube.n[ modelCube_index[t+2] ], &modelCube.c[i+2], p, &outCol1); NormalColorDpq(&modelCube.n[ modelCube_index[t+1] ], &modelCube.c[i+1], p, &outCol2); // Set vertex colors setRGB0(poly, outCol.r, outCol.g , outCol.b); setRGB1(poly, outCol1.r, outCol1.g, outCol1.b); setRGB2(poly, outCol2.r, outCol2.g, outCol2.b); // Sort the primitive into the OT OTz /= 3; if ((OTz > 0) && (OTz < OTLEN)) AddPrim(&ot[db][OTz-2], poly); nextpri += sizeof(POLY_G3); t+=3; } // Find and apply light rotation matrix // Find rotmat from light angles RotMatrix_gte(&lgtang, &rotlgt); // Find rotmat from cube angles RotMatrix_gte(&Rotate, &rotcube); // RotMatrix cube * RotMatrix light MulMatrix0(&rotcube, &rotlgt, &rotlgt); // Light Matrix * RotMatrix light MulMatrix0(&lgtmat, &rotlgt, &light); // Set new light matrix SetLightMatrix(&light); // Convert and set the matrices // Find Rotation matrix from object's angles RotMatrix(&Rotate, &Matrix); // Find Scale matrix from object's angles ScaleMatrix(&Matrix, &Scale); // Find Translation matrix from object's angles TransMatrix(&Matrix, &Trans); // Set GTE's rotation matrix SetRotMatrix(&Matrix); // Set GTE's Translation matrix SetTransMatrix(&Matrix); // Draw static cube t=0; for (i = 0; i < (modelCube1.len*3); i += 3) { poly1 = (POLY_G3 *)nextpri; SetPolyG3(poly1); OTz = RotTransPers(&modelCube1_mesh[modelCube1_index[t]] , (long*)&poly1->x0, &p, &Flag); OTz += RotTransPers(&modelCube1_mesh[modelCube1_index[t+2]], (long*)&poly1->x1, &p, &Flag); OTz += RotTransPers(&modelCube1_mesh[modelCube1_index[t+1]], (long*)&poly1->x2, &p, &Flag); CVECTOR outCol = { 0,0,0,0 }; CVECTOR outCol1 = { 0,0,0,0 }; CVECTOR outCol2 = { 0,0,0,0 }; NormalColorDpq(&modelCube1.n[ modelCube1_index[t+0] ], &modelCube1.c[i+0], p, &outCol); NormalColorDpq(&modelCube1.n[ modelCube1_index[t+2] ], &modelCube1.c[i+2], p, &outCol1); NormalColorDpq(&modelCube1.n[ modelCube1_index[t+1] ], &modelCube1.c[i+1], p, &outCol2); setRGB0(poly1, outCol.r, outCol.g , outCol.b); setRGB1(poly1, outCol1.r, outCol1.g, outCol1.b); setRGB2(poly1, outCol2.r, outCol2.g, outCol2.b); OTz /= 3; if ((OTz > 0) && (OTz < OTLEN)) AddPrim(&ot[db][OTz-2], poly1); nextpri += sizeof(POLY_G3); t+=3; } // See l.216 RotMatrix_gte(&lgtang, &rotlgt); RotMatrix_gte(&Rotate1, &rotcube); MulMatrix0(&rotcube, &rotlgt, &rotlgt); MulMatrix0(&lgtmat, &rotlgt, &light); SetLightMatrix(&light); // See l.227 RotMatrix(&Rotate1, &Matrix1); ScaleMatrix(&Matrix1, &Scale1); TransMatrix(&Matrix1, &Trans1); SetRotMatrix(&Matrix1); SetTransMatrix(&Matrix1); FntPrint("Hello lightsources !\n"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_mod/Makefile ================================================ TARGET = hello_mod SRCS = hello_mod.c \ src/mod.c \ ../thirdparty/nugget/modplayer/modplayer.c \ HIT/STAR.HIT \ include ../common.mk ================================================ FILE: hello_mod/README.md ================================================ This is an example of how to use [@nicolasnoble](https://github.com/nicolasnoble/)'s [modplayer](https://github.com/grumpycoders/pcsx-redux/tree/main/src/mips/modplayer) port in conjunction with the psyq libs. See the **wiki** for more details on MOD usage : [https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/MOD](https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/MOD). ## Credits & thanks Many thanks to @nicolasnobe for his work and help on getting this example working. MOD : stardust memories by Jester : https://modarchive.org/index.php?request=view_by_moduleid&query=59344 ================================================ FILE: hello_mod/hello_mod.c ================================================ // Play a MOD file converted to HIT // MOD Wiki page : https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/MOD #include #include #include #include #include #include // Mod playback #include "src/mod.h" #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256 #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define FONTX 960 #define FONTY 0 #define OTLEN 8 DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; short db = 1; // index of which buffer is used, values 0, 1 // Font color CVECTOR fntColor = { 128, 255, 0 }; CVECTOR fntColorBG = { 0, 0, 0 }; // MOD Playback #define MAXVOLUME 65536 #define INCVOLUME 256 // State enum PLAYBACK { STOP = 0, PLAY = 1, PAUSE = 2, }; // Music volume should range from 0 to 65536 u_int musicVolume = 2048; enum PLAYBACK state = PLAY; void init(void); void FntColor(CVECTOR fgcol, CVECTOR bgcol ); void display(void); void drawBG(void); void checkPad(void); void FntColor(CVECTOR fgcol, CVECTOR bgcol ) { // The debug font clut is at tx, ty + 128 // tx = bg color // tx + 1 = fg color // We can override the color by drawing a rect at these coordinates // RECT fg = { FONTX+1, FONTY + 128, 1, 1 }; RECT bg = { FONTX, FONTY + 128, 1, 1 }; ClearImage(&fg, fgcol.r, fgcol.g, fgcol.b); ClearImage(&bg, bgcol.r, bgcol.g, bgcol.b); } void init(void) { ResetCallback(); ResetGraph(0); // Initialize drawing engine with a complete reset (0) InitGeom(); SetGeomOffset(CENTERX,CENTERY); SetGeomScreen(CENTERX); SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1] SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1] SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1] SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1] // Set video mode #if VMODE SetVideoMode(MODE_PAL); disp[0].disp.y = 8; disp[1].disp.y = 8; #endif SetDispMask(1); // Display on screen setRGB0(&draw[0], 40, 40, 40); // set color for first draw area setRGB0(&draw[1], 40, 40, 40); // set color for second draw area draw[0].isbg = 1; // set mask for draw areas. 1 means repainting the area with the RGB color each frame draw[1].isbg = 1; PutDispEnv(&disp[db]); // set the disp and draw environnments PutDrawEnv(&draw[db]); FntLoad(FONTX, FONTY); // Load font to vram at 960,0(+128) FntOpen(16, 60, 288, 260, 0, 256 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars FntColor(fntColor, fntColorBG); } void display(void) { DrawSync(0); // Wait for all drawing to terminate VSync(0); // Wait for the next vertical blank PutDispEnv(&disp[db]); // set alternate disp and draw environnments PutDrawEnv(&draw[db]); db = !db; } void checkPad(void) { u_short pad = 0; static u_short oldPad; pad = PadRead(0); // Up if ( pad & PADLup && !(oldPad & PADLup) ) { MOD_PlaySoundEffect(11, 25, 15, 63); oldPad = pad; } if ( !(pad & PADLup) && oldPad & PADLup ) { oldPad = pad; } // Down if ( pad & PADLdown && !(oldPad & PADLdown) ) { MOD_PlaySoundEffect(12, 26, 15, 63); oldPad = pad; } if ( !(pad & PADLdown) && oldPad & PADLdown ) { oldPad = pad; } // Left if ( pad & PADLleft && !(oldPad & PADLleft) ) { MOD_PlaySoundEffect(13, 27, 15, 63); oldPad = pad; } if ( !(pad & PADLleft) && oldPad & PADLleft ) { oldPad = pad; } // Right if ( pad & PADLright && !(oldPad & PADLright) ) { // Channel 1 is transition anim, only take input when !transition MOD_PlaySoundEffect(6, 21, 15, 63); oldPad = pad; } if ( !(pad & PADLright) && oldPad & PADLright ) { oldPad = pad; } // Cross button if ( pad & PADRdown && !(oldPad & PADRdown) ) { // Select sound MOD_PlaySoundEffect(7, 22, 15, 63); oldPad = pad; } if ( !(pad & PADRdown) && oldPad & PADRdown ) { oldPad = pad; } // Square button if ( pad & PADRleft && !(oldPad & PADRleft) ) { // Select sound MOD_PlaySoundEffect(8, 23, 15, 63); oldPad = pad; } if ( !(pad & PADRleft) && oldPad & PADRleft ) { oldPad = pad; } // Circle button if ( pad & PADRright && !(oldPad & PADRright) ) { // Select sound MOD_PlaySoundEffect(9, 28, 15, 63); oldPad = pad; } if ( !(pad & PADRright) && oldPad & PADRright ) { oldPad = pad; } // Circle button if ( pad & PADRup && !(oldPad & PADRup) ) { // Select sound MOD_PlaySoundEffect(9, 24, 15, 63); oldPad = pad; } if ( !(pad & PADRup) && oldPad & PADRup ) { oldPad = pad; } // Select button : Stop mod / Play back from the start if ( pad & PADselect && !(oldPad & PADselect) ) { if ( state == PLAY ) { stopMusic(); state = STOP; } else if ( state == STOP ) { loadMod(); startMusic(); state = PLAY; } oldPad = pad; } if ( !(pad & PADselect) && oldPad & PADselect ) { oldPad = pad; } // Start button : Toggle playback pause if ( pad & PADstart && !(oldPad & PADstart) ) { if ( state == PLAY ) { pauseMusic(); state = PAUSE; } else if ( state == PAUSE ) { resumeMusic(); state = PLAY; } oldPad = pad; } if ( !(pad & PADstart) && oldPad & PADstart ) { oldPad = pad; } // R1/R2 : change music volume if ( pad & PADR1 && !(oldPad & PADR1) ) { if(musicVolume < MAXVOLUME) { musicVolume += INCVOLUME; } MOD_SetMusicVolume(musicVolume); oldPad = pad; } if ( !(pad & PADR1) && oldPad & PADR1 ) { oldPad = pad; } if ( pad & PADR2 && !(oldPad & PADR2) ) { if(musicVolume > 0) { musicVolume -= INCVOLUME; } MOD_SetMusicVolume(musicVolume); oldPad = pad; } if ( !(pad & PADR2) && oldPad & PADR2 ) { oldPad = pad; } } int main() { u_int t = 0; init(); PadInit(0); VSyncCallback(checkPad); // Mod Playback loadMod(); startMusic(); MOD_SetMusicVolume(musicVolume); // Main loop while (1) { // Timer t++; // Print stuff FntPrint("Hello mod ! %d\nUse the pad's buttons to\nplay sound effects.\n", t); FntPrint("State: %d, music volume : %d\n", state, musicVolume); FntPrint("Start : play/pause music.\n"); FntPrint("select : stop/play music.\n"); FntPrint("R1/R2 : Change music volume.\n"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_mod/src/mod.c ================================================ #include "mod.h" long musicEvent; typedef struct SpuVoiceVolume { short volL, volR; } SpuVoiceVolume; SpuVoiceVolume volumeState[24] = {0}; static void muteSPUvoices() { for (unsigned i = 0; i < 24; i++) { // Store current volume SpuGetVoiceVolume(i, &(volumeState[i].volL), &(volumeState[i].volR) ); // Mute SpuSetVoiceVolume(i, 0, 0); } } static void restoreSPUvoices() { for (unsigned i = 0; i < 24; i++) { // Restore volume SpuSetVoiceVolume(i, volumeState[i].volL, volumeState[i].volR ); } } // Playing a sound effect (aka mod note): https://discord.com/channels/642647820683444236/642848592754901033/898249196174458900 // Code by NicolasNoble : https://discord.com/channels/642647820683444236/663664210525290507/902624952715452436 void loadMod() { printf("Loading MOD:\'%s\'\n", HITFILE); MOD_Load((struct MODFileFormat*)HITFILE); printf("%02d Channels, %02d Orders\n", MOD_Channels, MOD_SongLength); } static long processMusic() { uint32_t old_hblanks = MOD_hblanks; MOD_Poll(); uint32_t new_hblanks = MOD_hblanks; if (old_hblanks != new_hblanks) SetRCnt(RCntCNT1, new_hblanks, RCntMdINTR); return MOD_hblanks; } void startMusic() { ResetRCnt(RCntCNT1); SetRCnt(RCntCNT1, MOD_hblanks, RCntMdINTR); StartRCnt(RCntCNT1); musicEvent = OpenEvent(RCntCNT1, EvSpINT, EvMdINTR, processMusic); EnableEvent(musicEvent); restoreSPUvoices(); } void pauseMusic() { muteSPUvoices(); DisableEvent(musicEvent); } void resumeMusic() { restoreSPUvoices(); EnableEvent(musicEvent); } void stopMusic() { MOD_Silence(); StopRCnt(RCntCNT1); DisableEvent(musicEvent); CloseEvent(musicEvent); } ================================================ FILE: hello_mod/src/mod.h ================================================ #pragma once #include #include #include "../../thirdparty/nugget/common/hardware/hwregs.h" #include "../../thirdparty/nugget/common/hardware/irq.h" #include "../../thirdparty/nugget/common/syscalls/syscalls.h" #define printf ramsyscall_printf // Mod Playback #include "../../thirdparty/nugget/modplayer/modplayer.h" extern const uint8_t _binary_HIT_STAR_HIT_start[]; #define HITFILE _binary_HIT_STAR_HIT_start extern long musicEvent; void loadMod(); void startMusic(); void pauseMusic(); void resumeMusic(); void stopMusic(); ================================================ FILE: hello_multi_vag/Makefile ================================================ TARGET = hello_multi_vag SRCS = hello_multi_vag.c \ ../VAG/hello.vag \ ../VAG/poly.vag \ ../VAG/0_come.vag \ ../VAG/1_cuek.vag \ ../VAG/2_erro.vag \ ../VAG/3_hehe.vag \ ../VAG/4_m4a1.vag \ ../VAG/5_punc.vag \ ../VAG/7_wron.vag \ ../VAG/8_yooo.vag \ include ../common.mk ================================================ FILE: hello_multi_vag/README.md ================================================ See here for more informations about the VAG fileformat and tools : https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/VAG ================================================ FILE: hello_multi_vag/hello_multi_vag.c ================================================ // Hello multi vag by Schnappy // August 2021 // // // Load VAG samples to the SPU sound buffer and play them back with pad input // // See here for more details, file format infos, tools, conversion : // https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/VAG // Docs : see libformat47.pdf p.209 // libover47.pdf, p.271 // libref47.pdf, p.980 #include #include #include #include #include // Sound system #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 #define SCREENYRES 240 #define CENTERX SCREENXRES/2 #define CENTERY SCREENYRES/2 #define MARGINX 0 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 7 // Text Field Height DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; short db = 0; // index of which buffer is used, values 0, 1 // Number of VAG files to load #define VAG_NBR 8 #define MALLOC_MAX VAG_NBR // Max number of time we can call SpuMalloc // convert Little endian to Big endian #define SWAP_ENDIAN32(x) (((x)>>24) | (((x)>>8) & 0xFF00) | (((x)<<8) & 0x00FF0000) | ((x)<<24)) // Memory management table ; allow MALLOC_MAX calls to SpuMalloc() - libref47.pdf p.1044 char spu_malloc_rec[SPU_MALLOC_RECSIZ * (MALLOC_MAX + 1)]; // Custom struct to handle VAG files typedef struct VAGsound { u_char * VAGfile; // Pointer to VAG data address u_long spu_channel; // SPU voice to playback to u_long spu_address; // SPU address for memory freeing spu mem } VAGsound; // VAG header struct (see fileformat47.pdf, p.209) typedef struct VAGhdr { // All the values in this header must be big endian char id[4]; // VAGp 4 bytes -> 1 char * 4 unsigned int version; // 4 bytes unsigned int reserved; // 4 bytes unsigned int dataSize; // (in bytes) 4 bytes unsigned int samplingFrequency;// 4 bytes char reserved2[12]; // 12 bytes -> 1 char * 12 char name[16]; // 16 bytes -> 1 char * 16 // Waveform data after that } VAGhdr; // SPU settings SpuCommonAttr commonAttributes; // structure for changing common voice attributes SpuVoiceAttr voiceAttributes ; // structure for changing individual voice attributes // extern VAG files extern u_char _binary____VAG_0_come_vag_start; extern u_char _binary____VAG_1_cuek_vag_start; extern u_char _binary____VAG_2_erro_vag_start; extern u_char _binary____VAG_3_hehe_vag_start; extern u_char _binary____VAG_4_m4a1_vag_start; extern u_char _binary____VAG_5_punc_vag_start; extern u_char _binary____VAG_7_wron_vag_start; extern u_char _binary____VAG_8_yooo_vag_start; // soundBank VAGsound soundBank[VAG_NBR] = { { &_binary____VAG_0_come_vag_start, SPU_00CH, 0 }, { &_binary____VAG_1_cuek_vag_start, SPU_01CH, 0 }, { &_binary____VAG_2_erro_vag_start, SPU_02CH, 0 }, { &_binary____VAG_3_hehe_vag_start, SPU_03CH, 0 }, { &_binary____VAG_4_m4a1_vag_start, SPU_04CH, 0 }, { &_binary____VAG_5_punc_vag_start, SPU_05CH, 0 }, { &_binary____VAG_7_wron_vag_start, SPU_06CH, 0 }, { &_binary____VAG_8_yooo_vag_start, SPU_07CH, 0 } }; // Prototypes void initGraph(void); void display(void); void initSnd(void); u_long sendVAGtoSPU(unsigned int VAG_data_size, u_char *VAG_data); void setVoiceAttr(unsigned int pitch, long channel, unsigned long soundAddr ); u_long setSPUtransfer(VAGsound * sound); void playSFX(VAGsound * sound); void initGraph(void) { ResetGraph(0); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], 50, 50, 50); setRGB0(&draw[1], 50, 50, 50); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(8, 60, 304, 200, 0, 500 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); db = !db; } // Audio initialisation & functions void initSnd(void){ SpuInitMalloc(MALLOC_MAX, spu_malloc_rec); // Maximum number of blocks, mem. management table address. commonAttributes.mask = (SPU_COMMON_MVOLL | SPU_COMMON_MVOLR); // Mask which attributes to set commonAttributes.mvol.left = 0x3fff; // Master volume left commonAttributes.mvol.right = 0x3fff; // see libref47.pdf, p.1058 SpuSetCommonAttr(&commonAttributes); // set attributes SpuSetIRQ(SPU_OFF); SpuSetKey(SpuOff, SPU_ALLCH); } u_long sendVAGtoSPU(unsigned int VAG_data_size, u_char *VAG_data){ u_long transferred; SpuSetTransferMode(SpuTransByDMA); // DMA transfer; can do other processing during transfer transferred = SpuWrite (VAG_data + sizeof(VAGhdr), VAG_data_size); // transfer VAG_data_size bytes from VAG_data address to sound buffer SpuIsTransferCompleted (SPU_TRANSFER_WAIT); // Checks whether transfer is completed and waits for completion return transferred; } void setVoiceAttr(unsigned int pitch, long channel, unsigned long soundAddr ){ voiceAttributes.mask= //~ Attributes (bit string, 1 bit per attribute) ( SPU_VOICE_VOLL | SPU_VOICE_VOLR | SPU_VOICE_PITCH | SPU_VOICE_WDSA | SPU_VOICE_ADSR_AMODE | SPU_VOICE_ADSR_SMODE | SPU_VOICE_ADSR_RMODE | SPU_VOICE_ADSR_AR | SPU_VOICE_ADSR_DR | SPU_VOICE_ADSR_SR | SPU_VOICE_ADSR_RR | SPU_VOICE_ADSR_SL ); voiceAttributes.voice = channel; //~ Voice (low 24 bits are a bit string, 1 bit per voice ) voiceAttributes.volume.left = 0x0; //~ Volume voiceAttributes.volume.right = 0x0; //~ Volume voiceAttributes.pitch = pitch; //~ Interval (set pitch) voiceAttributes.addr = soundAddr; //~ Waveform data start address voiceAttributes.a_mode = SPU_VOICE_LINEARIncN; //~ Attack rate mode = Linear Increase - see libref47.pdf p.1091 voiceAttributes.s_mode = SPU_VOICE_LINEARIncN; //~ Sustain rate mode = Linear Increase voiceAttributes.r_mode = SPU_VOICE_LINEARDecN; //~ Release rate mode = Linear Decrease voiceAttributes.ar = 0x0; //~ Attack rate voiceAttributes.dr = 0x0; //~ Decay rate voiceAttributes.rr = 0x0; //~ Release rate voiceAttributes.sr = 0x0; //~ Sustain rate voiceAttributes.sl = 0xf; //~ Sustain level SpuSetVoiceAttr(&voiceAttributes); // set attributes } u_long setSPUtransfer(VAGsound * sound){ // Return spu_address u_long transferred, spu_address; u_int pitch; const VAGhdr * VAGheader = (VAGhdr *) sound->VAGfile; pitch = (SWAP_ENDIAN32(VAGheader->samplingFrequency) << 12) / 44100L; spu_address = SpuMalloc(SWAP_ENDIAN32(VAGheader->dataSize)); // Allocate an area of dataSize bytes in the sound buffer. SpuSetTransferStartAddr(spu_address); // Sets a starting address in the sound buffer transferred = sendVAGtoSPU(SWAP_ENDIAN32(VAGheader->dataSize), sound->VAGfile); setVoiceAttr(pitch, sound->spu_channel, spu_address); // Return 1 if ok, size transferred else. //~ if (transferred == SWAP_ENDIAN32(VAGheader->dataSize)){ //~ return 1; //~ } //~ return transferred; return spu_address; } void playSFX(VAGsound * sound){ // Set voice volume to max voiceAttributes.mask= ( SPU_VOICE_VOLL | SPU_VOICE_VOLR ); voiceAttributes.voice = sound->spu_channel; voiceAttributes.volume.left = 0x1000; voiceAttributes.volume.right = 0x1000; SpuSetVoiceAttr(&voiceAttributes); // Play voice SpuSetKey(SpuOn, sound->spu_channel); } int main(void) { // Store input values int pad, oldpad; u_long spu_address; // Init all the things ! initGraph(); // Init Spu SpuInit(); // Init sound settings initSnd(); // Init Pad PadInit(0); // Transfer all VAGs to SPU RAM // Beware that the SPU only has 512KB of RAM so a 'soundbank' should stay within that limit. // If more is needed, a second soundbank could be loaded on demand. for (u_short vag = 0; vag < VAG_NBR; vag++ ){ soundBank[vag].spu_address = setSPUtransfer(&soundBank[vag]); } while (1) { // Pad // Use up, down, left, right, triangle, cross, circle, square to play various samples // Read pad values pad = PadRead(0); if (pad & PADLleft && !(oldpad & PADLleft)){ playSFX(&soundBank[0]); oldpad = pad; } if (pad & PADLright && !(oldpad & PADLright)){ playSFX(&soundBank[1]); oldpad = pad; } if (pad & PADLup && !(oldpad & PADLup)){ playSFX(&soundBank[2]); oldpad = pad; } if (pad & PADLdown && !(oldpad & PADLdown)){ playSFX(&soundBank[3]); oldpad = pad; } if (pad & PADRleft && !(oldpad & PADRleft)){ playSFX(&soundBank[4]); oldpad = pad; } if (pad & PADRright && !(oldpad & PADRright)){ playSFX(&soundBank[5]); oldpad = pad; } if (pad & PADRup && !(oldpad & PADRup)){ playSFX(&soundBank[6]); oldpad = pad; } if (pad & PADRdown && !(oldpad & PADRdown)){ playSFX(&soundBank[7]); oldpad = pad; } // Reset oldpad if (!(pad & PADRdown) || !(pad & PADRup) || !(pad & PADRleft) || !(pad & PADRright) || !(pad & PADLdown) || !(pad & PADLup) || !(pad & PADLleft) || !(pad & PADLright) ){ oldpad = pad; } FntPrint("Hello multi vag ! %d\n", VSync(-1)); FntPrint("Use the pad to play various samples.\n"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_multi_xa/Makefile ================================================ .PHONY: all cleansub all: mkpsxiso -y ./isoconfig.xml cleansub: $(MAKE) clean rm -f hello_multi_xa.cue hello_multi_xa.bin TARGET = hello_multi_xa SRCS = hello_multi_xa.c \ include ../common.mk ================================================ FILE: hello_multi_xa/README.md ================================================ ## Multi XA samples playback Use an interleaved XA file to store multiple sound effects and use pre-calculated offset to play them back. Use up, down, left, right, triangle, cross, circle, square to play various samples. If this seems complicated, first study the simple XA playback example : https://github.com/ABelliqueux/nolibgs_hello_worlds/tree/main/hello_xa You need [mkpsxiso](https://github.com/Lameguy64/mkpsxiso) in your $PATH to generate a PSX disk image. You also need [ffmpeg](https://ffmpeg.org/), [`psxavenc` and `xainterleave`](https://github.com/ABelliqueux/candyk-psx/tree/master/toolsrc/). ### Compile This will compile and build an iso image : ```bash make ``` ### Clean directory ```bash make cleansub ``` ## Producing an interleaved XA sound bank ### Sound to WAV conversion with ffmpeg ```bash ffmpeg -i input.mp3 -acodec pcm_s16le -ac 2 -ar 44100 output.wav ``` ### WAV to XA conversion See further down how you should adapt the `-F` and `-C` parameters. ```bash psxavenc -f 37800 -t xa -b 4 -c 2 -F 1 -C 0 input.wav output.xa ``` ### XA to multi XA track You can use a concatenation tool to build your tracks like so : ```bash cat track1.xa track2.xa track3.xa > channelX.xa ``` On windows, use : ``` copy track1.xa+track2.xa+track3.xa channelX.xa ``` ### XA to interleaved XA Use `xainterleave` as instructed here : https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/XA#interleaving-xa-files ## Multi XA specifics ## Silence file You should use a silent XA file to intersperse between your samples, so that you have a margin of error to avoid pops and noises.I.e: ```bash cat track1.xa silence.xa track3.xa silence.xa track4.xa > channelX.xa ``` ### XA file size and vertical density An interleaved XA (4 or 8 channels) file will have the size of its largest channel multiplied by the number of channels. Therefore, you should try to obtain a vertical density on all channels for your sample, i.e : That : ``` channel 0 sample1.xa channel 1 sample2.xa channel 2 sample3.xa channel 3 sample4.xa ``` instead of : ``` channel 0 sample1.xa sample2.xa sample2.xa sample4.xa channel 1 channel 2 channel 3 ``` You should use your longest sample as a base duration, and create the other channels to obtain a similar duration, i.e : ``` channel 0 <------------- sample1.xa ---------------> <-- silence.xa --> <-sample2.xa -> channel 1 <-- sample3.xa --> <-- silence.xa --> < -------- sample4.xa -----------> ``` ### Calculating sample's start and end offsets Using the size of your XA sample file, you can find it's size in sector like so : `(size/2336)-1 * 8` For example, file with size `84096B` is `(((84096/2336) == 36) - 1 == 35) * 8 == 280`. First sample's start will then be `0`, and end `280`. The following sample's start position will have an offset of `8`, hence `288`, etc. #### Example These are the calculations for the files on 4 channels ``` // channel 1 // name size start end 5_come.xa 18688 0 56 5_sile_h.xa 23360 64 136 5_erro.xa 44384 144 288 // channel 2 6_cuek.xa 32704 0 104 6_sile_h.xa 23360 112 184 6_hehe.xa 56064 196 380 6_sile_h.xa 23360 388 460 6_wron.xa 53728 468 644 // channel 3 7_m4a1.xa 84096 0 280 7_sile_h.xa 23360 288 360 7_punch.xa 16352 368 416 // channel 4 8_yooo.xa 114464 0 384 ``` and the corresponding structure in code : ```c XAbank soundBank = { 8, // index 0, // file offset { // channel 5 // id size file channel start end cursor { 0, 18688, 0, 5, 0, 56, -1 }, // Ommit the silence.xa file { 1, 44384, 0, 5 , 144, 288, -1 }, // channel 6 { 2, 32704, 0, 6 , 0, 104, -1 }, // Ommit the silence.xa file { 3, 56064, 0, 6 , 196, 380, -1 }, // Ommit the silence.xa file { 4, 53728, 0, 6 , 468, 644, -1 }, // channel 7 { 5, 84096, 0, 7 , 0, 260, -1 }, // Ommit the silence.xa file { 6, 16352, 0, 7 , 368, 440, -1 }, // channel 8 { 7, 114464, 0, 8 , 0, 384, -1 } } }; ``` ## Tools, sources and documentation See the wiki for general informations, tools and sources about the XA format : https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/XA ## Sound credits All the sound effects come from https://www.myinstants.com and are : ``` comedy_pop_finger_in_mouth_001(1).mp3 cuek.swf.mp3 erro.mp3 m4a1_single-kibblesbob-8540445.mp3 punch.mp3 wrong-answer-sound-effect.mp3 yooooooooooooooooooooooooo_4.mp3 hehehehhehehehhehehheheehehe.mp3 ``` ================================================ FILE: hello_multi_xa/hello_multi_xa.c ================================================ // XA multi sample playback example // // Use an interleaved XA file to store multiple sound effects and use pre-calculated offset to play them back. // Use up, down, left, right, triangle, cross, circle, square to play various samples. // If this seems complicated, first study the simple XA playback example : https://github.com/ABelliqueux/nolibgs_hello_worlds/tree/main/hello_xa // // base on `psyq/addons/scee/CD/XAPLAYER` // Refs : http://psx.arthus.net/code/XA/XATUT.pdf // http://psx.arthus.net/code/XA/xatut.zip // http://psx.arthus.net/code/XA/XA%20ADPCM%20documentation.txt // Schnappy 2021 #include #include #include #include #include // CD library #include // SPU library #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 + (VMODE << 4) // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256 #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 0 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 16 // Text Field Height DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; short db = 0; // index of which buffer is used, values 0, 1 // SPU attributes SpuCommonAttr spuSettings; #define CD_SECTOR_SIZE 2048 // XA // Sector offset for XA data 4: simple speed, 8: double speed #define XA_CHANNELS 8 #define XA_CDSPEED XA_CHANNELS >> 2 // Number of XA samples ( != # of XA files ) #define XA_TRACKS 8 typedef struct XAsound { u_int id; u_int size; // We can find size in sector : size / 2336, start value begins at 23, end value is at start + offset ( (size / 2336)-1 * #channels ) // subsequent start value have an 8 bytes offset (n-1.end + 8) u_char file, channel; u_int start, end; int cursor; } XAsound; typedef struct XAbank { u_int index; int offset; XAsound samples[]; } XAbank; XAbank soundBank = { 8, 0, { // channel 5 // id size file channel start end cursor { 0, 18688, 0, 5, 0, 56, -1 }, { 1, 44384, 0, 5 , 144, 288, -1 }, // channel 6 { 2, 32704, 0, 6 , 0, 104, -1 }, { 3, 56064, 0, 6 , 196, 380, -1 }, { 4, 53728, 0, 6 , 468, 644, -1 }, // channel 7 { 5, 84096, 0, 7 , 0, 260, -1 }, { 6, 16352, 0, 7 , 368, 440, -1 }, // channel 8 { 7, 114464, 0, 8 , 0, 384, -1 } } }; // XA file to load static char * loadXA = "\\INTER8.XA;1"; // File informations : pos, size, name CdlFILE XAPos = {0}; // CD filter CdlFILTER filter; // File position in m/s/f CdlLOC loc; void init(void) { ResetGraph(0); // Initialize drawing engine with a complete reset (0) SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1] SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1] SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1] SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1] // Set video mode if (VMODE){ SetVideoMode(MODE_PAL);} SetDispMask(1); // Display on screen setRGB0(&draw[0], 50, 50, 50); // set color for first draw area setRGB0(&draw[1], 50, 50, 50); // set color for second draw area draw[0].isbg = 1; // set mask for draw areas. 1 means repainting the area with the RGB color each frame draw[1].isbg = 1; PutDispEnv(&disp[db]); // set the disp and draw environnments PutDrawEnv(&draw[db]); FntLoad(960, 0); // Load font to vram at 960,0(+128) FntOpen(MARGINX, MARGINY, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 512 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars } void display(void) { DrawSync(0); // Wait for all drawing to terminate VSync(0); // Wait for the next vertical blank PutDispEnv(&disp[db]); // set alternate disp and draw environnments PutDrawEnv(&draw[db]); db = !db; // flip db value (0 or 1) } void spuSetup(SpuCommonAttr * spuSettings) { // Init Spu SpuInit(); // Set master & CD volume to max spuSettings->mask = (SPU_COMMON_MVOLL | SPU_COMMON_MVOLR | SPU_COMMON_CDVOLL | SPU_COMMON_CDVOLR | SPU_COMMON_CDMIX); spuSettings->mvol.left = 0x6000; spuSettings->mvol.right = 0x6000; spuSettings->cd.volume.left = 0x6000; spuSettings->cd.volume.right = 0x6000; // Enable CD input ON spuSettings->cd.mix = SPU_ON; // Apply settings SpuSetCommonAttr(spuSettings); // Set transfer mode SpuSetTransferMode(SPU_TRANSFER_BY_DMA); } void XAsetup() { u_char param[4]; // ORing the parameters we need to set ; drive speed, ADPCM play, Subheader filter, sector size // If using CdlModeSpeed(Double speed), you need to load an XA file that has 8 channels. // In single speed, a 4 channels XA is to be used. param[0] = CdlModeSpeed|CdlModeRT|CdlModeSF|CdlModeSize1; // Issue primitive command to CD-ROM system (Blocking-type) // Set the parameters above CdControlB(CdlSetmode, param, 0); // Pause at current pos CdControlF(CdlPause,0); } int main(void) { // Init display init(); // Set SPU spuSetup(&spuSettings); // Init CD system CdInit(); // Pad PadInit(0); // Load XA file from cd // Find XA file pos CdSearchFile( &XAPos, loadXA); // Set cd head to start of file soundBank.offset = CdPosToInt(&XAPos.pos); // Set cd XA playback parameters and pause cd XAsetup(); // Pad variables to avoid multifire int pad, oldpad; // Keep track of XA Sample currently playing int sample = -1; while (1) // infinite loop { // if sample is set if (sample != -1 ){ // Begin XA file playback... // if sample's cursor is 0 if (soundBank.samples[sample].cursor == 0){ // Convert sector number to CD position in min/second/frame and set CdlLOC accordingly. CdIntToPos( soundBank.samples[sample].start + soundBank.offset , &loc); // Send CDROM read command CdControlF(CdlReadS, (u_char *)&loc); // Set playing flag } // if sample's cursor is close to sample's end position, stop playback if ((soundBank.samples[sample].cursor += XA_CDSPEED) >= soundBank.samples[sample].end - soundBank.samples[sample].start ){ CdControlF(CdlStop,0); soundBank.samples[sample].cursor = -1; sample = -1; } } // Pad // Use up, down, left, right, triangle, cross, circle, square to play various samples // Read pad values pad = PadRead(0); // Left if (pad & PADLleft && !(oldpad & PADLleft)){ // Set sample ID for playback sample = 7; // Change file/channel in the filter struct filter.chan = soundBank.samples[sample].channel; filter.file = soundBank.samples[sample].file; // Set filter CdControlF(CdlSetfilter, (u_char *)&filter); // Reset sample's cursor soundBank.samples[sample].cursor = 0; // Store pad value for release oldpad = pad; } if (pad & PADLright && !(oldpad & PADLright)){ sample = 6; filter.chan = soundBank.samples[sample].channel; filter.file = soundBank.samples[sample].file; // Set filter CdControlF(CdlSetfilter, (u_char *)&filter); soundBank.samples[sample].cursor = 0; oldpad = pad; } if (pad & PADLup && !(oldpad & PADLup)){ sample = 5; filter.chan = soundBank.samples[sample].channel; filter.file = soundBank.samples[sample].file; // Set filter CdControlF(CdlSetfilter, (u_char *)&filter); soundBank.samples[sample].cursor = 0; oldpad = pad; } if (pad & PADLdown && !(oldpad & PADLdown)){ sample = 4; filter.chan = soundBank.samples[sample].channel; filter.file = soundBank.samples[sample].file; // Set filter CdControlF(CdlSetfilter, (u_char *)&filter); soundBank.samples[sample].cursor = 0; oldpad = pad; } if (pad & PADRleft && !(oldpad & PADRleft)){ sample = 3; filter.chan = soundBank.samples[sample].channel; filter.file = soundBank.samples[sample].file; // Set filter CdControlF(CdlSetfilter, (u_char *)&filter); soundBank.samples[sample].cursor = 0; oldpad = pad; } if (pad & PADRright && !(oldpad & PADRright)){ sample = 2; filter.chan = soundBank.samples[sample].channel; filter.file = soundBank.samples[sample].file; // Set filter CdControlF(CdlSetfilter, (u_char *)&filter); soundBank.samples[sample].cursor = 0; oldpad = pad; } if (pad & PADRup && !(oldpad & PADRup)){ sample = 1; filter.chan = soundBank.samples[sample].channel; filter.file = soundBank.samples[sample].file; // Set filter CdControlF(CdlSetfilter, (u_char *)&filter); soundBank.samples[sample].cursor = 0; oldpad = pad; } if (pad & PADRdown && !(oldpad & PADRdown)){ sample = 0; filter.chan = soundBank.samples[sample].channel; filter.file = soundBank.samples[sample].file; // Set filter CdControlF(CdlSetfilter, (u_char *)&filter); soundBank.samples[sample].cursor = 0; oldpad = pad; } // Reset oldpad if (!(pad & PADRdown) || !(pad & PADRup) || !(pad & PADRleft) || !(pad & PADRright) || !(pad & PADLdown) || !(pad & PADLup) || !(pad & PADLleft) || !(pad & PADLright) ){oldpad = pad;} FntPrint("Hello multi XA ! %d\n", VSync(-1)); FntPrint("Use the pad to play various samples.\n"); for (int i=0;i"); } FntPrint("%d: %d %d\n", i, soundBank.samples[i].start, soundBank.samples[i].end); } FntPrint("Cursor: %d\n", soundBank.samples[sample].cursor ); FntPrint("File offset: %d\n", soundBank.offset ); FntFlush(-1); // Draw print stream display(); // Execute display() } return 0; } ================================================ FILE: hello_multi_xa/isoconfig.xml ================================================ ================================================ FILE: hello_multi_xa/system.cnf ================================================ BOOT=cdrom:\SCES_313.37;1 TCB=4 EVENT=10 STACK=801FFFF0 ================================================ FILE: hello_multi_xa/xa/interleave8.txt ================================================ 1 xa channel5.xa 0 5 1 xa channel6.xa 0 6 1 xa channel7.xa 0 7 1 xa channel8.xa 0 8 1 null 1 null 1 null 1 null ================================================ FILE: hello_ovl_exec/Makefile ================================================ .PHONY: all cleansub all: $(MAKE) -C hello_poly clean all $(MAKE) -C hello_ovl_world clean all $(MAKE) -C hello_tile clean all mkpsxiso -y ./isoconfig.xml cleansub: $(MAKE) -C hello_poly clean $(MAKE) -C hello_tile clean $(MAKE) -C hello_ovl_world clean $(MAKE) clean rm -f hello_ovl_exec.cue hello_ovl_exec.bin TARGET = hello_ovl_exec OVERLAYSCRIPT ?= overlay.ld OVERLAYSECTION ?= .ovly0 .ovly1 .ovly2 SRCS = hello_ovl_exec.c \ hello_ovl_world/hello_ovl_world.c \ hello_tile/hello_ovl_tile.c \ hello_poly/hello_ovl_poly.c \ include ../common.mk ================================================ FILE: hello_ovl_exec/README.md ================================================ This example shows how to load overlays from the CD and execute them. Many thanks to @nicolasnobe for his help on getting this example working. See the wiki for more details on overlays in the context of PSX development : [https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/OVL](https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/OVL) ================================================ FILE: hello_ovl_exec/common.h ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #include "../thirdparty/nugget/common/syscalls/syscalls.h" #define printf ramsyscall_printf #define setRGB(p, r0, g0, b0) \ (p)->r = r0, (p)->g = g0, (p)->b = b0 #define VMODE 0 //~ #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES (240 + (VMODE << 4)) // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256 #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 0 // margins for text display #define MARGINY 32 #define FONTSIZE 18 * 7 // Text Field Height #define OTLEN 8 // Ordering Table Length extern DRAWENV draw[2]; extern char primbuff[2][32768]; extern char *nextpri; extern u_long ot[2][OTLEN]; extern uint8_t db; extern uint8_t useOT; extern CVECTOR BGcolor; enum OverlayNumber { MOTHERSHIP = -1, OVERLAY_HELLO = 0, OVERLAY_TILE = 1, OVERLAY_POLY = 2, }; extern enum OverlayNumber next_overlay; extern void init(void); extern void display(void); ================================================ FILE: hello_ovl_exec/hello_ovl_exec.c ================================================ // Load overlay files from CD and execute them // Schnappy 10-2021 // With the help of Nicolas Noble and impiaa // https://discord.com/channels/642647820683444236/663664210525290507/894624082367229952 #include "common.h" DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; char primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes u_long ot[2][OTLEN]; char *nextpri = primbuff[0]; uint8_t db = 0; CVECTOR BGcolor, MScolor = { 224, 108, 76 }; extern u_long load_all_overlays_here; typedef struct Overlay { char filename[0x7c]; int (*main)(); char commandline[0x180]; CVECTOR BGcolor; } Overlay; int ovl_main_hello(); int ovl_main_tile(); int ovl_main_poly(); Overlay g_chainload[] = { {"\\HELLO.OVL;1", ovl_main_hello, "", { 225, 220, 40 }}, {"\\TILE.OVL;1", ovl_main_tile, "", { 150, 30 , 40 }}, {"\\POLY.OVL;1", ovl_main_poly, "", { 60 , 127, 0 }}, }; enum OverlayNumber next_overlay = MOTHERSHIP; enum OverlayNumber prev_overlay = MOTHERSHIP; int main(void); int loadOverlayAndStart(Overlay * overlay); void EmptyOTag(u_long ot[2][OTLEN]); void EmptyPrimBuf(char primbuff[2][32768], char ** nextpri); void preInitOvl(Overlay * overlay); void postInitOvl(Overlay * overlay); void clearVRAM(void); void (*dispp) (void); uint8_t useOT = 0; int CDreadOK = 0; void init(void) { ResetCallback(); ResetGraph(0); // Initialize drawing engine with a complete reset (0) // Initialize and setup the GTE InitGeom(); SetGeomOffset(CENTERX,CENTERY); SetGeomScreen(CENTERX); SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1] SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1] SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1] SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1] // Set video mode if (VMODE){ SetVideoMode(MODE_PAL);} SetDispMask(1); // Display on screen setRGB0(&draw[0], BGcolor.r, BGcolor.g, BGcolor.b); // set color for first draw area setRGB0(&draw[1], BGcolor.r, BGcolor.g, BGcolor.b); // set color for second draw area draw[0].isbg = 1; // set mask for draw areas. 1 means repainting the area with the RGB color each frame draw[1].isbg = 1; PutDispEnv(&disp[db]); // set the disp and draw environnments PutDrawEnv(&draw[db]); FntLoad(960, 0); // Load font to vram at 960,0(+128) FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars } void display(void) { DrawSync(0); // Wait for all drawing to terminate VSync(0); // Wait for the next vertical blank PutDispEnv(&disp[db]); // set alternate disp and draw environnments PutDrawEnv(&draw[db]); if (useOT) { DrawOTag(&ot[db][OTLEN - 1]); } db = !db; // flip db value (0 or 1) nextpri = primbuff[db]; } void clearVRAM(void) { RECT vram = {0,0,1024,512}; ClearImage(&vram,0,0,0); } void preInitOvl(Overlay * overlay) { //~ clearVRAM(); ResetCallback(); ResetGraph(3); EmptyPrimBuf(primbuff, &nextpri); EmptyOTag(&ot[db]); setRGB(&BGcolor, overlay->BGcolor.r, overlay->BGcolor.g, overlay->BGcolor.b ); } void postInitOvl(Overlay * overlay) { //~ clearVRAM(); //~ ResetGraph(3); EmptyPrimBuf(primbuff, &nextpri); EmptyOTag(&ot[db]); setRGB(&BGcolor, MScolor.r, MScolor.g, MScolor.b); setRGB0(&draw[0], BGcolor.r, BGcolor.g, BGcolor.b); // set color for first draw area setRGB0(&draw[1], BGcolor.r, BGcolor.g, BGcolor.b); FntLoad(960, 0); // Load font to vram at 960,0(+128) FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars } int loadOverlayAndStart(Overlay * overlay) { int CDreadResult = 0; int next_ovl = -1; // Load overlay file CDreadResult = CdReadFile(overlay->filename, &load_all_overlays_here, 0); CdReadSync(0, 0); printf( "CD read: %d", CDreadResult); // If file loaded sucessfully if (CDreadResult) { // Pre OVL preInitOvl(overlay); // Exec OVL next_ovl = overlay->main(); // Post OVL postInitOvl(overlay); } return next_ovl; } void EmptyPrimBuf(char primbuff[2][32768], char ** nextpri) { for(uint16_t p; p < 32768; p++) { primbuff[0][p] = 0; primbuff[1][p] = 0; } *nextpri = primbuff[0]; } void EmptyOTag(u_long ot[2][OTLEN]) { for (uint16_t p; p < OTLEN; p++) { ot[0][p] = 0; ot[1][p] = 0; } } int main(void) { int t = 0; setRGB(&BGcolor, MScolor.r, MScolor.g, MScolor.b); // init() display init(); // Init CD system CdInit(); prev_overlay = next_overlay; while(1){ if (t == 100){ next_overlay = OVERLAY_HELLO; } if (next_overlay != -1 && next_overlay != prev_overlay){ prev_overlay = next_overlay; next_overlay = loadOverlayAndStart(&g_chainload[next_overlay]); t = 0; } t++; FntPrint("Mothership: %d\nOvl: %d %d", t, prev_overlay, next_overlay); FntFlush(-1); display(); } // return 0; } ================================================ FILE: hello_ovl_exec/hello_ovl_world/Makefile ================================================ TARGET = hello_world SRCS = hello_world.c \ CPPFLAGS+=-DSTANDALONE include ../../common.mk ================================================ FILE: hello_ovl_exec/hello_ovl_world/hello_ovl_world.c ================================================ #include "../common.h" int ovl_main_hello(void) { init(); int i = 0; while(1) { i++; #ifndef STANDALONE if (i==100){ next_overlay = OVERLAY_TILE; break; } FntPrint("Hello world ! %d\n", i); #else FntPrint("Hello standalone ! %d\n", i); #endif FntFlush(-1); display(); } return next_overlay; }; ================================================ FILE: hello_ovl_exec/hello_ovl_world/hello_world.c ================================================ #include "../common.h" DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768];// double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] uint8_t db = 0; // index of which buffer is used, values 0, 1 CVECTOR BGcolor = { 0, 255, 100 }; //~ int next_overlay; void init(void) { ResetGraph(0); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], BGcolor.r, BGcolor.g, BGcolor.b); // set color for first draw area setRGB0(&draw[1], BGcolor.r, BGcolor.g, BGcolor.b); // set color for second draw area draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } #include "hello_ovl_world.c" int main(void) { ovl_main_hello(); }; ================================================ FILE: hello_ovl_exec/hello_poly/Makefile ================================================ TARGET = hello_poly SRCS = hello_poly.c \ CPPFLAGS+=-DSTANDALONE include ../../common.mk ================================================ FILE: hello_ovl_exec/hello_poly/hello_ovl_poly.c ================================================ #include "../common.h" int ovl_main_poly(void) { #ifndef STANDALONE useOT = 1; #endif init(); uint16_t timer = 0; uint16_t timeout = 100; MATRIX IDMATRIX = {0}; POLY_F4 *poly = {0}; SVECTOR RotVector = {0, 0, 0}; VECTOR MovVector = {0, 0, CENTERX, 0}; SVECTOR VertPos[4] = { {-32, -32, 1 }, {-32, 32, 1 }, { 32, -32, 1 }, { 32, 32, 1 } }; MATRIX PolyMatrix = IDMATRIX; long polydepth; long polyflag; long OTz; init(); while (1) { ClearOTagR(ot[db], OTLEN); poly = (POLY_F4 *)nextpri; // Set poly to point to the address of the next primitiv in the buffer // Set transform matrices for this polygon RotMatrix(&RotVector, &PolyMatrix); // Apply rotation matrix TransMatrix(&PolyMatrix, &MovVector); SetRotMatrix(&PolyMatrix); // Set default rotation matrix SetTransMatrix(&PolyMatrix); // Set default transformation matrix setPolyF4(poly); // Initialize poly as a POLY_F4 setRGB0(poly, 255, 255, 0); // Set poly color // RotTransPers OTz = RotTransPers4( &VertPos[0], &VertPos[1], &VertPos[2], &VertPos[3], (long*)&poly->x0, (long*)&poly->x1, (long*)&poly->x2, (long*)&poly->x3, &polydepth, &polyflag ); // Perform coordinate and perspective transformation for 4 vertices RotVector.vy += 4; RotVector.vz += 4; // Apply rotation on Z-axis. On PSX, the Z-axis is pointing away from the screen. //~ addPrim(ot[db], poly); // add poly to the Ordering table addPrim(ot[db][OTLEN-1], poly); // add poly to the Ordering table nextpri += sizeof(POLY_F4); // increment nextpri address with size of a POLY_F4 struct timer++; FntPrint("Hello Poly ! %d", timer); FntFlush(-1); #ifndef STANDALONE if (timer == timeout){ useOT = 0; next_overlay = MOTHERSHIP; break; } #endif display(); } return next_overlay; }; ================================================ FILE: hello_ovl_exec/hello_poly/hello_poly.c ================================================ #include "../common.h" DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] uint8_t db = 0; // index of which buffer is used, values 0, 1 CVECTOR BGcolor = { 50, 255, 150 }; void init(void) { ResetCallback(); ResetGraph(3); // Initialize and setup the GTE InitGeom(); SetGeomOffset(CENTERX,CENTERY); SetGeomScreen(CENTERX); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } setRGB0(&draw[0], BGcolor.r, BGcolor.g, BGcolor.b); // set color for first draw area setRGB0(&draw[1], BGcolor.r, BGcolor.g, BGcolor.b); // set color for second draw area SetDispMask(1); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } #include "hello_ovl_poly.c" int main(void) { ovl_main_poly(); } ================================================ FILE: hello_ovl_exec/hello_tile/Makefile ================================================ TARGET = hello_tile SRCS = hello_tile.c \ CPPFLAGS+=-DSTANDALONE include ../../common.mk ================================================ FILE: hello_ovl_exec/hello_tile/hello_ovl_tile.c ================================================ #include "../common.h" int ovl_main_tile(void) { #ifndef STANDALONE useOT = 1; #endif uint16_t timer = 0; uint16_t timeout = 100; TILE * blue_tile = 0; TILE * pink_tile = 0; // This one is added at a different OT index TILE * yellow_tile = 0; init(); while(1) { // Initialize the reversed ordering table. This means the elements at index OTLEN - 1 is drawn first. ClearOTagR(ot[db], OTLEN); // yellow_tile yellow_tile = (TILE * ) nextpri; // yellow_tile is a pointer to primbuf content at adress nextpri, that's cast (type converted) to a TILE struc. setTile(yellow_tile); // initialize the TILE structure ( fill the length and tag(?) value ) setXY0(yellow_tile, CENTERX - 32 , CENTERY - 48); // Set X,Y setWH(yellow_tile, 128, 40); // Set Width, Height setRGB0(yellow_tile, 255, 255, 0); // Set color addPrim(ot[db][OTLEN - 1], yellow_tile); // Add primitive to ordering table nextpri += sizeof(TILE); // blue_tile blue_tile = (TILE * ) nextpri; // blue_tile is a pointer to primbuf content at adress nextpri, that's cast (type converted) to a blue_tile struc. setTile(blue_tile); // initialize the blue_tile structure ( fill the length and tag(?) value ) setXY0(blue_tile, CENTERX - 16, CENTERY - 32); // Set X,Y setWH(blue_tile, 32, 64); // Set Width, Height setRGB0(blue_tile, 60, 180, 255); // Set color addPrim(ot[db][OTLEN - 2], blue_tile); // Add primitive to ordering table nextpri += sizeof(TILE); // Increment the adress nextpri points to by the size of TILE struct // pink_tile pink_tile = (TILE * ) nextpri; // pink_tile is a pointer to primbuf content at adress nextpri, that's cast (type converted) to a TILE struc. setTile(pink_tile); // initialize the TILE structure ( fill the length and tag(?) value ) setXY0(pink_tile, CENTERX, CENTERY - 64); // Set X,Y setWH(pink_tile, 64, 64); // Set Width, Height setRGB0(pink_tile, 255, 32, 255); // Set color addPrim(ot[db][OTLEN - 2], pink_tile); // Add primitive to ordering table nextpri += sizeof(TILE); timer ++; FntPrint("Hello tile ! %d\n", timer); FntFlush(-1); #ifndef STANDALONE if (timer == timeout){ useOT = 0; next_overlay = OVERLAY_POLY; break; } #endif display(); } return next_overlay; }; ================================================ FILE: hello_ovl_exec/hello_tile/hello_tile.c ================================================ #include "../common.h" DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] uint8_t db = 0; // index of which buffer is used, values 0, 1 CVECTOR BGcolor = { 50, 255, 150 }; void init(void) { ResetGraph(0); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], BGcolor.r, BGcolor.g, BGcolor.b); // set color for first draw area setRGB0(&draw[1], BGcolor.r, BGcolor.g, BGcolor.b); // set color for second draw area draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); // We're using a reverse OT, so we want to display the last item first. See PsyQ's LibRef47.pdf, p.277 DrawOTag(&ot[db][OTLEN - 1]); // Comment above line, and uncomment the following line to use a regular oredered OT. Comment l.71 and Uncomment l.73 accordingly db = !db; nextpri = primbuff[db]; } #include "hello_ovl_tile.c" int main(void) { ovl_main_tile(); }; ================================================ FILE: hello_ovl_exec/isoconfig.xml ================================================ ================================================ FILE: hello_ovl_exec/overlay.ld ================================================ __heap_base = MAX(__ovly0_end, __ovly2_end); SECTIONS { OVERLAY __bss_end : NOCROSSREFS SUBALIGN(4) { .ovlyload { load_all_overlays_here = .; } .ovly0 { KEEP(hello_poly/*.o(.text)) __ovly0_ctor = .; KEEP(hello_poly/*.o(.text.startup._GLOBAL__*)) KEEP(hello_poly/*.o(.text.*)) KEEP(hello_poly/*.o(.rodata*)) KEEP(hello_poly/*.o(.sdata*)) KEEP(hello_poly/*.o(.data*)) KEEP(hello_poly/*.o(.sbss*)) KEEP(hello_poly/*.o(.bss*)) KEEP(hello_poly/*.o(.ctors)) . = ALIGN(4); __ovly0_end = .; } .ovly1 { KEEP(hello_tile/*.o(.text)) __ovly1_ctor = .; KEEP(hello_tile/*.o(.text.startup._GLOBAL__*)) KEEP(hello_tile/*.o(.text.*)) KEEP(hello_tile/*.o(.rodata*)) KEEP(hello_tile/*.o(.sdata*)) KEEP(hello_tile/*.o(.data*)) KEEP(hello_tile/*.o(.sbss*)) KEEP(hello_tile/*.o(.bss*)) KEEP(hello_tile/*.o(.ctors)) . = ALIGN(4); __ovly1_end = .; } .ovly2 { KEEP(hello_ovl_world/*.o(.text)) __ovly2_ctor = .; KEEP(hello_ovl_world/*.o(.text.startup._GLOBAL__*)) KEEP(hello_ovl_world/*.o(.text.*)) KEEP(hello_ovl_world/*.o(.rodata*)) KEEP(hello_ovl_world/*.o(.sdata*)) KEEP(hello_ovl_world/*.o(.data*)) KEEP(hello_ovl_world/*.o(.sbss*)) KEEP(hello_ovl_world/*.o(.bss*)) KEEP(hello_ovl_world/*.o(.ctors)) . = ALIGN(4); __ovly2_end = .; } } } ================================================ FILE: hello_ovl_exec/system.cnf ================================================ BOOT=cdrom:\SCES_313.37;1 TCB=4 EVENT=10 STACK=801FFFF0 ================================================ FILE: hello_pad/Makefile ================================================ TARGET = hello_pad SRCS = hello_pad.c \ include ../common.mk ================================================ FILE: hello_pad/hello_pad.c ================================================ // hello_pad example // // We're using libetc PadInit() and PadRead() that only supports the 16 buttons pad // but doesn't need the libpad lib. It's fine for prototyping and simple stuff. // Schnappy 2021 #include #include #include #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 #define SCREENYRES 240 #define CENTERX SCREENXRES/2 #define CENTERY SCREENYRES/2 #define MARGINX 32 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 2 // Text Field Height #define OTLEN 8 // Ordering Table Length DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] short db = 0; // index of which buffer is used, values 0, 1 void init(void) { ResetGraph(0); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], 50, 50, 50); setRGB0(&draw[1], 50, 50, 50); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } int main(void) { TILE * PADL; // Tile primitives TILE * TRIGGERL; TILE * PADR; TILE * TRIGGERR; TILE * START, * SELECT; int pad = 0; init(); PadInit(0); // Initialize pad. Mode is always 0 while (1) { ClearOTagR(ot[db], OTLEN); // D-cross PADL = (TILE *)nextpri; setTile(PADL); setRGB0(PADL, 0, 0, 255); setXY0(PADL, CENTERX - 80, CENTERY); setWH(PADL, 24, 24); addPrim(ot[db], PADL); nextpri += sizeof(TILE); // L1+L2 TRIGGERL = (TILE *)nextpri; setTile(TRIGGERL); setRGB0(TRIGGERL, 255, 0, 0); setXY0(TRIGGERL, CENTERX - 80, CENTERY - 80); setWH(TRIGGERL, 24, 24); addPrim(ot[db], TRIGGERL); nextpri += sizeof(TILE); // /\, X, O, [] PADR = (TILE *)nextpri; setTile(PADR); setRGB0(PADR, 0, 255, 0); setXY0(PADR, CENTERX + 50, CENTERY); setWH(PADR, 24, 24); addPrim(ot[db], PADR); nextpri += sizeof(TILE); // R1+R2 TRIGGERR = (TILE *)nextpri; setTile(TRIGGERR); setRGB0(TRIGGERR, 255, 0, 255); setXY0(TRIGGERR, CENTERX + 50, CENTERY -80); setWH(TRIGGERR, 24, 24); addPrim(ot[db], TRIGGERR); nextpri += sizeof(TILE); // START + SELECT START = (TILE *)nextpri; setTile(START); setRGB0(START, 240, 240, 240); setXY0(START, CENTERX - 16, CENTERY - 36); setWH(START, 24, 24); addPrim(ot[db], START); nextpri += sizeof(TILE); // Pad stuff pad = PadRead(0); // Read pads input. id is unused, always 0. // PadRead() returns a 32 bit value, where input from pad 1 is stored in the low 2 bytes and input from pad 2 is stored in the high 2 bytes. (https://matiaslavik.wordpress.com/2015/02/13/diving-into-psx-development/) // D-pad if(pad & PADLup) {PADL->y0 = CENTERY - 16;} // 🡩 // To access pad 2, use ( pad >> 16 & PADLup)... if(pad & PADLdown) {PADL->y0 = CENTERY + 16;} // 🡫 if(pad & PADLright){PADL->x0 = CENTERX - 64;} // 🡪 if(pad & PADLleft) {PADL->x0 = CENTERX - 96;} // 🡨 // Buttons if(pad & PADRup) {PADR->y0 = CENTERY - 16;} // △ if(pad & PADRdown) {PADR->y0 = CENTERY + 16;} // ╳ if(pad & PADRright){PADR->x0 = CENTERX + 66;} // ⭘ if(pad & PADRleft) {PADR->x0 = CENTERX + 34;} // ⬜ // Shoulder buttons if(pad & PADL1){TRIGGERL->y0 = CENTERY - 64;} // L1 if(pad & PADL2){TRIGGERL->y0 = CENTERY - 96;} // L2 if(pad & PADR1){TRIGGERR->y0 = CENTERY - 64;} // R1 if(pad & PADR2){TRIGGERR->y0 = CENTERY - 96;} // R2 // Start & Select if(pad & PADstart){START->w = 32; START->h = 32;START->x0 -= 4;START->y0 -= 4;} // START if(pad & PADselect){START->r0 = 0;} // SELECT FntPrint("Hello Pad!"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_poly/Makefile ================================================ TARGET = hello_poly SRCS = hello_poly.c \ include ../common.mk ================================================ FILE: hello_poly/hello_poly.c ================================================ // Draw a colored poly primitive // // With help from Nicolas Noble, Jaby smoll Seamonstah, Lameguy64 // // From ../psyq/addons/graphics/MESH/RMESH/TUTO0.C : // Schnappy 2021 #include #include #include #include #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 // Screen height #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 0 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 7 // Text Field Height #define OTLEN 8 // Ordering Table Length DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] short db = 0; // index of which buffer is used, values 0, 1 void init(void) { ResetGraph(0); // Initialize and setup the GTE InitGeom(); SetGeomOffset(CENTERX,CENTERY); SetGeomScreen(CENTERX); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].disp.y = disp[1].disp.y = 8; } SetDispMask(1); setRGB0(&draw[0], 50, 50, 50); setRGB0(&draw[1], 50, 50, 50); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } int main(void) { POLY_F4 *poly = {0}; // pointer to a POLY_F4 SVECTOR RotVector = {0, 0, 0}; // Initialize rotation vector {x, y, z} VECTOR MovVector = {0, 0, CENTERX, 0}; // Initialize translation vector {x, y, z} VECTOR ScaleVector ={ONE, ONE, ONE}; // ONE is define as 4096 in libgte.h SVECTOR VertPos[4] = { // Set initial vertices position relative to 0,0 - see here : https://psx.arthus.net/docs/poly_f4.jpg {-32, -32, 1 }, // Vert 1 {-32, 32, 1 }, // Vert 2 { 32, -32, 1 }, // Vert 3 { 32, 32, 1 } // Vert 4 }; MATRIX PolyMatrix = {0}; long polydepth; long polyflag; long OTz; init(); while (1) { ClearOTagR(ot[db], OTLEN); poly = (POLY_F4 *)nextpri; // Set poly to point to the address of the next primitiv in the buffer // Set transform matrices for this polygon RotMatrix(&RotVector, &PolyMatrix); // Apply rotation matrix TransMatrix(&PolyMatrix, &MovVector); ScaleMatrix(&PolyMatrix, &ScaleVector); // Apply translation matrix SetRotMatrix(&PolyMatrix); // Set default rotation matrix SetTransMatrix(&PolyMatrix); // Set default transformation matrix setPolyF4(poly); // Initialize poly as a POLY_F4 setRGB0(poly, 255, 0, 255); // Set poly color // RotTransPers using 4 calls //~ OTz = RotTransPers(&VertPos[0], (long*)&poly->x0, &polydepth, &polyflag); //~ RotTransPers(&VertPos[1], (long*)&poly->x1, &polydepth, &polyflag); //~ RotTransPers(&VertPos[2], (long*)&poly->x2, &polydepth, &polyflag); //~ RotTransPers(&VertPos[3], (long*)&poly->x3, &polydepth, &polyflag); // RotTransPers4 equivalent OTz = RotTransPers4( &VertPos[0], &VertPos[1], &VertPos[2], &VertPos[3], (long*)&poly->x0, (long*)&poly->x1, (long*)&poly->x2, (long*)&poly->x3, &polydepth, &polyflag ); // Perform coordinate and perspective transformation for 4 vertices RotVector.vy += 4; RotVector.vz += 4; // Apply rotation on Z-axis. On PSX, the Z-axis is pointing away from the screen. addPrim(ot[db], poly); // add poly to the Ordering table nextpri += sizeof(POLY_F4); // increment nextpri address with size of a POLY_F4 struct FntPrint("Hello Poly !"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_poly_ft/Makefile ================================================ TARGET = hello_poly_ft SRCS = hello_poly_ft.c \ ../TIM/bousai.tim \ include ../common.mk ================================================ FILE: hello_poly_ft/hello_poly_ft.c ================================================ // Draw a textured poly primitive // // With help from Nicolas Noble, Jaby smoll Seamonstah, Lameguy64 // // From ../psyq/addons/graphics/MESH/RMESH/TUTO0.C : // Schnappy 2021 #include #include #include #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 // Screen height #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 32 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 5 // Text Field Height #define OTLEN 8 // Ordering Table Length DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] short db = 0; // index of which buffer is used, values 0, 1 // 16bpp TIM extern unsigned long _binary____TIM_bousai_tim_start[]; extern unsigned long _binary____TIM_bousai_tim_end[]; extern unsigned long _binary____TIM_bousai_tim_length; TIM_IMAGE bousai; void LoadTexture(u_long * tim, TIM_IMAGE * tparam){ // This part is from Lameguy64's tutorial series : lameguy64.net/svn/pstutorials/chapter1/3-textures.html login/pw: annoyingmous OpenTIM(tim); // Open the tim binary data, feed it the address of the data in memory ReadTIM(tparam); // This read the header of the TIM data and sets the corresponding members of the TIM_IMAGE structure LoadImage(tparam->prect, tparam->paddr); // Transfer the data from memory to VRAM at position prect.x, prect.y DrawSync(0); // Wait for the drawing to end if (tparam->mode & 0x8){ // check 4th bit // If 4th bit == 1, TIM has a CLUT LoadImage(tparam->crect, tparam->caddr); // Load it to VRAM at position crect.x, crect.y DrawSync(0); // Wait for drawing to end } } void init(void) { ResetGraph(0); // Initialize and setup the GTE InitGeom(); SetGeomOffset(CENTERX,CENTERY); SetGeomScreen(CENTERX); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], 128, 128, 128); setRGB0(&draw[1], 128, 128, 128); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } int main(void) { POLY_FT4 *poly = {0}; // pointer to a POLY_G4 SVECTOR RotVector = {0, 0, 0}; // Initialize rotation vector {x, y, z} VECTOR MovVector = {0, 0, CENTERX/2, 0}; // Initialize translation vector {x, y, z, pad} VECTOR ScaleVector = {ONE, ONE, ONE}; // ONE is define as 4096 in libgte.h SVECTOR VertPos[4] = { // Set initial vertices position relative to 0,0 - see here : https://psx.arthus.net/docs/poly_f4.jpg {-32, -32, 1 }, // Vert 1 {-32, 32, 1 }, // Vert 2 { 32, -32, 1 }, // Vert 3 { 32, 32, 1 } // Vert 4 }; MATRIX PolyMatrix = {0}; long polydepth; long polyflag; int ping = 0; init(); LoadTexture(_binary____TIM_bousai_tim_start, &bousai); while (1) { ClearOTagR(ot[db], OTLEN); poly = (POLY_FT4 *)nextpri; // Set poly to point to the address of the next primitiv in the buffer // Set transform matrices for this polygon RotMatrix(&RotVector, &PolyMatrix); // Apply rotation matrix TransMatrix(&PolyMatrix, &MovVector); // Apply translation matrix ScaleMatrix(&PolyMatrix, &ScaleVector); // Apply scale matrix SetRotMatrix(&PolyMatrix); // Set default rotation matrix SetTransMatrix(&PolyMatrix); // Set default transformation matrix setPolyFT4(poly); // Initialize poly as a POLY_F4 poly->tpage = getTPage(bousai.mode&0x3, 0, bousai.prect->x, bousai.prect->y); // Get Tpage coordinates from the TIM_IMAGE mode and prect members. setRGB0(poly, 128, 128, 128); // Set poly color (neutra here) RotTransPers4( &VertPos[0], &VertPos[1], &VertPos[2], &VertPos[3], (long*)&poly->x0, (long*)&poly->x1, (long*)&poly->x2, (long*)&poly->x3, &polydepth, &polyflag ); // Perform coordinate and perspective transformation for 4 vertices setUV4(poly, 0, 0, 0, 144, 144, 0, 144, 144); // Set UV coordinates in order Top Left, Bottom Left, Top Right, Bottom Right // Let's have some fun on the Z axis if(!ping){ if (MovVector.vz < CENTERX){ // While Poly position on Z axis is < 160, push it MovVector.vz += 1; // Push on Z axis } else { ping = !ping; // Switch ping value } } if(ping){ if (MovVector.vz > CENTERX/2){ // While Poly position on Z axis is > 80, pull it MovVector.vz -= 1; // Pull on Z axis } else { ping = !ping; // Switch ping value } } addPrim(ot[db], poly); // add poly to the Ordering table nextpri += sizeof(POLY_FT4); // increment nextpri address with size of a POLY_F4 struct FntPrint("Hello textured poly !"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_poly_fun/Makefile ================================================ TARGET = hello_poly_fun SRCS = hello_poly_fun.c \ include ../common.mk ================================================ FILE: hello_poly_fun/hello_poly_fun.c ================================================ // Having fun with polygons, matrices and vectors // Credits : Schnappy //With great help from Jaby smoll Seamonstah, Nicolas Noble, NDR008, paul, sickle on https://discord.com/invite/Zd82yXvs // 11/2020 #include #include #include #include #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SPIN 16 // Rotation speed increment #define SCREENXRES 320 #define SCREENYRES 240 #define CENTERX SCREENXRES/2 #define CENTERY SCREENYRES/2 #define MARGINX 10 // margins for text display #define MARGINY 4 #define FONTSIZE 8 * 7 // Text Field Height #define OTLEN 16 // Ordering Table Length DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] short db = 0; // index of which buffer is used, values 0, 1 CVECTOR BgColor[3] = {20, 20, 20}; struct polygon { POLY_F4 * poly_f4; CVECTOR color; short width; short height; SVECTOR RotV_L; VECTOR TransV_L; VECTOR ScaleV_L; SVECTOR PivotV_L; SVECTOR Verts[4]; MATRIX Matrix; long depth; long flag; short rotSpeed; int otz; }; void init(void) { ResetGraph(0); // Initialize and setup the GTE : Not needed ? InitGeom(); SetGeomOffset(CENTERX,CENTERY); SetGeomScreen(CENTERX); PadInit(0); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], BgColor->r, BgColor->g, BgColor->b); setRGB0(&draw[1], BgColor->r, BgColor->g, BgColor->b); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } void pivotPoint(SVECTOR VertPos[3],short width,short height, SVECTOR pivot){ // Not very efficient I think VertPos[0].vx = -pivot.vx; VertPos[0].vy = -pivot.vy; VertPos[0].vz = 1; VertPos[1].vx = width - pivot.vx; VertPos[1].vy = -pivot.vy; VertPos[1].vz = 1; VertPos[2].vx = -pivot.vx; VertPos[2].vy = height-pivot.vy; VertPos[2].vz = 1; VertPos[3].vx = width - pivot.vx; VertPos[3].vy = height - pivot.vy; VertPos[3].vz = 1; } int main(void) { u_short BtnTimer = 0; // Timer to limit pad input rate u_short polyCount = 1; // current polygon index int otz; // z-index struct polygon *CurrentPoly; // points to the address of selected polygon // White cursor : shows which polygon is selected struct polygon cursorS = { cursorS.poly_f4, {255, 255, 255}, // color 30, 30, // width, height {0,0,0}, // RotV_L {0,0,0, 0}, // TransV_L {4096,4096,4096}, // ScaleV_L {1,1,1}, // PivotV { // Verts[4] {-1, -1, 1}, { 1, -1, 1}, {-1, 1, 1}, { 1, 1, 1} }, {0} // Matrix }; //Red struct polygon polyS = { polyS.poly_f4, {255, 0, 0}, // color 30, 30, // width, height {0,0,0}, // RotV_L {-48, -30, 0, 0}, // TransV_L {4096,4096,4096}, // ScaleV_L {15,15,1}, // PivotV { // Verts[4] {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0} }, {0}, // Matrix 0,0, // depth, flag 8, // rotSpeed 1 // z-index }; //Yellow struct polygon poly1S = { poly1S.poly_f4, {255, 187, 0}, // color 28, 28, // width, height {0,0,0}, // RotV_L {-20, 10, 0, 0}, // TransV_L {4096,4096,4096}, // ScaleV_L {4,4,1}, // PivotV { // Verts[4] {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0} }, {0}, // Matrix 0,0, // depth, flag -12, // rotSpeed 2 // z-index }; //Green struct polygon poly2S = { poly2S.poly_f4, {0, 255, 153}, // color 24, 24, // width, height {0,0,0}, // RotV_L {36, -10, 0, 0}, // TransV_L {4096,4096,4096}, // ScaleV_L {12,12,1}, // PivotV { // Verts[4] {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0} }, {0}, // Matrix 0,0, // depth, flag -6, // rotSpeed 3 // z-index }; //Blue struct polygon poly3S = { poly3S.poly_f4, {112, 254, 254}, // color 26, 26, // width, height {0,0,0}, // RotV_L {20, 20, 0, 0}, // TransV_L {4096,4096,4096}, // ScaleV_L {13,13,1}, // PivotV { // Verts[4] {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0} }, {0}, // Matrix 0,0, //depth, flag 256, //rotSpeed 4 // z-index }; ///// CurrentPoly = &polyS; pivotPoint(polyS.Verts, polyS.width, polyS.height, polyS.PivotV_L); pivotPoint(poly1S.Verts, poly1S.width, poly1S.height, poly1S.PivotV_L); pivotPoint(poly2S.Verts, poly2S.width, poly2S.height, poly2S.PivotV_L); pivotPoint(poly3S.Verts, poly3S.width, poly3S.height, poly3S.PivotV_L); init(); while (1) { ClearOTagR(ot[db], OTLEN); cursorS.poly_f4 = (POLY_F4 *)nextpri; RotMatrix(&cursorS.RotV_L , &cursorS.Matrix); TransMatrix(&cursorS.Matrix, &CurrentPoly->TransV_L); SetRotMatrix(&cursorS.Matrix); SetTransMatrix(&cursorS.Matrix); setPolyF4(cursorS.poly_f4); setRGB0(cursorS.poly_f4,cursorS.color.r,cursorS.color.g,cursorS.color.b); //~ setXY4(cursorS, MovVector.vx-1, MovVector.vy-1 ,MovVector.vx + 1, MovVector.vy -1,MovVector.vx-1, MovVector.vy+1,MovVector.vx+1, MovVector.vy+1); RotTransPers4( &cursorS.Verts[0], &cursorS.Verts[1], &cursorS.Verts[2], &cursorS.Verts[3], (long*)&cursorS.poly_f4->x0, (long*)&cursorS.poly_f4->x1, (long*)&cursorS.poly_f4->x2, (long*)&cursorS.poly_f4->x3, &cursorS.depth, &cursorS.flag ); addPrim(ot[db], cursorS.poly_f4); nextpri += sizeof(POLY_F4); ///// Red polyS.poly_f4 = (POLY_F4 *)nextpri; polyS.RotV_L.vz += polyS.rotSpeed; RotMatrix(&polyS.RotV_L, &polyS.Matrix); TransMatrix(&polyS.Matrix, &polyS.TransV_L); ScaleMatrix(&polyS.Matrix, &polyS.ScaleV_L); SetRotMatrix(&polyS.Matrix); SetTransMatrix(&polyS.Matrix); setPolyF4(polyS.poly_f4); setRGB0(polyS.poly_f4, polyS.color.r,polyS.color.g,polyS.color.b); RotTransPers4( &polyS.Verts[0], &polyS.Verts[1], &polyS.Verts[2], &polyS.Verts[3], (long*)&polyS.poly_f4->x0, (long*)&polyS.poly_f4->x1, (long*)&polyS.poly_f4->x2, (long*)&polyS.poly_f4->x3, &polyS.depth, &polyS.flag ); addPrim(ot[db][polyS.otz], polyS.poly_f4); nextpri += sizeof(POLY_F4); ///// Yellow poly1S.poly_f4 = (POLY_F4 *)nextpri; poly1S.RotV_L.vz += poly1S.rotSpeed; RotMatrix(&poly1S.RotV_L, &poly1S.Matrix); TransMatrix(&poly1S.Matrix, &poly1S.TransV_L); ScaleMatrix(&poly1S.Matrix, &poly1S.ScaleV_L); SetRotMatrix(&poly1S.Matrix); SetTransMatrix(&poly1S.Matrix); setPolyF4(poly1S.poly_f4); setRGB0(poly1S.poly_f4, poly1S.color.r,poly1S.color.g,poly1S.color.b); RotTransPers4( &poly1S.Verts[0], &poly1S.Verts[1], &poly1S.Verts[2], &poly1S.Verts[3], (long*)&poly1S.poly_f4->x0, (long*)&poly1S.poly_f4->x1, (long*)&poly1S.poly_f4->x2, (long*)&poly1S.poly_f4->x3, &poly1S.depth, &poly1S.flag ); addPrim(ot[db][poly1S.otz], poly1S.poly_f4); nextpri += sizeof(POLY_F4); ///// Green poly2S.poly_f4 = (POLY_F4 *)nextpri; poly2S.RotV_L.vz += poly2S.rotSpeed; RotMatrix(&poly2S.RotV_L, &poly2S.Matrix); TransMatrix(&poly2S.Matrix, &poly2S.TransV_L); ScaleMatrix(&poly2S.Matrix, &poly2S.ScaleV_L); SetRotMatrix(&poly2S.Matrix); SetTransMatrix(&poly2S.Matrix); setPolyF4(poly2S.poly_f4); setRGB0(poly2S.poly_f4, poly2S.color.r,poly2S.color.g,poly2S.color.b); RotTransPers4( &poly2S.Verts[0], &poly2S.Verts[1], &poly2S.Verts[2], &poly2S.Verts[3], (long*)&poly2S.poly_f4->x0, (long*)&poly2S.poly_f4->x1, (long*)&poly2S.poly_f4->x2, (long*)&poly2S.poly_f4->x3, &poly2S.depth, &poly2S.flag ); addPrim(ot[db][poly2S.otz], poly2S.poly_f4); nextpri += sizeof(POLY_F4); ///// Blue poly3S.poly_f4 = (POLY_F4 *)nextpri; poly3S.RotV_L.vz += poly3S.rotSpeed; RotMatrix(&poly3S.RotV_L, &poly3S.Matrix); TransMatrix(&poly3S.Matrix, &poly3S.TransV_L); ScaleMatrix(&poly3S.Matrix, &poly3S.ScaleV_L); SetRotMatrix(&poly3S.Matrix); SetTransMatrix(&poly3S.Matrix); setPolyF4(poly3S.poly_f4); setRGB0(poly3S.poly_f4, poly3S.color.r,poly3S.color.g,poly3S.color.b); RotTransPers4( &poly3S.Verts[0], &poly3S.Verts[1], &poly3S.Verts[2], &poly3S.Verts[3], (long*)&poly3S.poly_f4->x0, (long*)&poly3S.poly_f4->x1, (long*)&poly3S.poly_f4->x2, (long*)&poly3S.poly_f4->x3, &poly3S.depth, &poly3S.flag ); addPrim(ot[db][poly3S.otz], poly3S.poly_f4); nextpri += sizeof(POLY_F4); // Pad stuff int pad = PadRead(0); // init pad // Right D-pad if(pad & PADRup){ if (CurrentPoly->PivotV_L.vy >= 0){ CurrentPoly->PivotV_L.vy -= 1; pivotPoint(CurrentPoly->Verts, CurrentPoly->width, CurrentPoly->height, CurrentPoly->PivotV_L); } else { CurrentPoly->PivotV_L.vy = CurrentPoly->PivotV_L.vy; } }; if(pad & PADRdown){ if (CurrentPoly->PivotV_L.vy <= CurrentPoly->height ){ CurrentPoly->PivotV_L.vy += 1; pivotPoint(CurrentPoly->Verts, CurrentPoly->width, CurrentPoly->height, CurrentPoly->PivotV_L); } else { CurrentPoly->PivotV_L.vy = CurrentPoly->PivotV_L.vy; } }; if(pad & PADRleft){ if (CurrentPoly->PivotV_L.vx >= 0){ CurrentPoly->PivotV_L.vx -= 1; pivotPoint(CurrentPoly->Verts, CurrentPoly->width, CurrentPoly->height, CurrentPoly->PivotV_L); } else { CurrentPoly->PivotV_L.vx = CurrentPoly->PivotV_L.vx; } }; if(pad & PADRright){ if (CurrentPoly->PivotV_L.vx <= CurrentPoly->width ){ CurrentPoly->PivotV_L.vx += 1; pivotPoint(CurrentPoly->Verts, CurrentPoly->width, CurrentPoly->height, CurrentPoly->PivotV_L); } else { CurrentPoly->PivotV_L.vx = CurrentPoly->PivotV_L.vx; } }; // R1, R2, L2, L2 if(pad & PADR1){ if(BtnTimer == 0){ if (polyCount < 4){ CurrentPoly -= 1; BtnTimer = 10; polyCount++; } else { CurrentPoly = &polyS + 1; polyCount = 0; } } } if(pad & PADR2){ if(BtnTimer == 0){ if(CurrentPoly->otz < 5 ){ CurrentPoly->otz += 1; BtnTimer = 10; } else { CurrentPoly->otz = 1; BtnTimer = 10; } } } if(pad & PADL1){ if(BtnTimer == 0){ if (CurrentPoly->rotSpeed <= 320){ CurrentPoly->rotSpeed += 8; } BtnTimer = 10; } } if(pad & PADL2){ if(BtnTimer == 0){ if (CurrentPoly->rotSpeed >= -320){ CurrentPoly->rotSpeed -= 8; } BtnTimer = 10; } } // Left D-Pad if(pad & PADLup){ if(BtnTimer == 0){ CurrentPoly->TransV_L.vy -= 1; //~ BtnTimer = 2; } } if(pad & PADLdown){ if(BtnTimer == 0){ CurrentPoly->TransV_L.vy += 1; //~ BtnTimer = 2; } } if(pad & PADLleft){ if(BtnTimer == 0){ CurrentPoly->TransV_L.vx -= 1; //~ BtnTimer = 2; } } if(pad & PADLright){ if(BtnTimer == 0){ CurrentPoly->TransV_L.vx += 1; //~ BtnTimer = 2; } } if(pad & PADstart){ if(BtnTimer == 0){ CurrentPoly->ScaleV_L.vx += 100; CurrentPoly->ScaleV_L.vy += 100; //~ CurrentPoly->TransV_L.vz += 1; } } if(pad & PADselect){ if(BtnTimer == 0){ CurrentPoly->ScaleV_L.vx -= 100; CurrentPoly->ScaleV_L.vy -= 100; //~ CurrentPoly->TransV_L.vz -= 1; } } // Btn_timer decrement if(BtnTimer > 0){ BtnTimer -= 1; } // On-screen instructions FntPrint("\ D-Pad:move polygon.\n\ [],X,O,\/\\ : Move pivot point.\n\ L1,L2 : Rotations speed +/-\n\ R1 : select polygon\n\ R2 : change z-index\n\ Start,Select : Scale polygon +/-\ "); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_poly_gt/Makefile ================================================ TARGET = hello_poly_gt SRCS = hello_poly_gt.c \ ../TIM/bousai.tim \ include ../common.mk ================================================ FILE: hello_poly_gt/hello_poly_gt.c ================================================ // Draw a textured poly primitive with gouraud shading // // With help from Nicolas Noble, Jaby smoll Seamonstah, Lameguy64 // // From ../psyq/addons/graphics/MESH/RMESH/TUTO0.C : // Schnappy 2021 #include #include #include #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 // Screen height #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 32 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 5 // Text Field Height #define OTLEN 8 // Ordering Table Length DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] short db = 0; // index of which buffer is used, values 0, 1 // 16bpp TIM extern unsigned long _binary____TIM_bousai_tim_start[]; extern unsigned long _binary____TIM_bousai_tim_end[]; extern unsigned long _binary____TIM_bousai_tim_length; TIM_IMAGE bousai; void LoadTexture(u_long * tim, TIM_IMAGE * tparam){ // This part is from Lameguy64's tutorial series : lameguy64.net/svn/pstutorials/chapter1/3-textures.html login/pw: annoyingmous OpenTIM(tim); // Open the tim binary data, feed it the address of the data in memory ReadTIM(tparam); // This read the header of the TIM data and sets the corresponding members of the TIM_IMAGE structure LoadImage(tparam->prect, tparam->paddr); // Transfer the data from memory to VRAM at position prect.x, prect.y DrawSync(0); // Wait for the drawing to end if (tparam->mode & 0x8){ // check 4th bit // If 4th bit == 1, TIM has a CLUT LoadImage(tparam->crect, tparam->caddr); // Load it to VRAM at position crect.x, crect.y DrawSync(0); // Wait for drawing to end } } void init(void) { ResetGraph(0); // Initialize and setup the GTE InitGeom(); SetGeomOffset(CENTERX,CENTERY); SetGeomScreen(CENTERX); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], 128, 128, 128); setRGB0(&draw[1], 128, 128, 128); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } int main(void) { int i; POLY_GT4 *poly = {0}; // pointer to a POLY_G4 SVECTOR RotVector = {0, 0, 0}; // Initialize rotation vector {x, y, z} VECTOR MovVector = {0, 0, 120, 0}; // Initialize translation vector {x, y, z} SVECTOR VertPos[4] = { // Set initial vertices position relative to 0,0 - see here : https://psx.arthus.net/docs/poly_f4.jpg {-32, -32, 0 }, // Vert 1 {-32, 32, 0 }, // Vert 2 { 32, -32, 0 }, // Vert 3 { 32, 32, 0 } // Vert 4 }; MATRIX PolyMatrix = {0}; DR_TPAGE * bousai_tpage; long polydepth; long polyflag; init(); LoadTexture(_binary____TIM_bousai_tim_start, &bousai); while (1) { ClearOTagR(ot[db], OTLEN); poly = (POLY_GT4 *)nextpri; // Set poly to point to the address of the next primitiv in the buffer // Set transform matrices for this polygon RotMatrix(&RotVector, &PolyMatrix); // Apply rotation matrix TransMatrix(&PolyMatrix, &MovVector); // Apply translation matrix SetRotMatrix(&PolyMatrix); // Set default rotation matrix SetTransMatrix(&PolyMatrix); // Set default transformation matrix setPolyGT4(poly); // Initialize poly as a POLY_F4 poly->tpage = getTPage(bousai.mode&0x3, 0, bousai.prect->x, bousai.prect->y); setRGB0(poly, 128, 128, 128); // Set vertice 1 color setRGB1(poly, 255, 0, 0); // Set vertice 2 color setRGB2(poly, 0, 255, 0); // Set vertice 3 color setRGB3(poly, 0, 0, 255); // Set vertice 4 color RotTransPers4( &VertPos[0], &VertPos[1], &VertPos[2], &VertPos[3], (long*)&poly->x0, (long*)&poly->x1, (long*)&poly->x2, (long*)&poly->x3, &polydepth, &polyflag ); // Perform coordinate and perspective transformation for 4 vertices setUV4(poly, 0, 0, 0, 144, 144, 0, 144, 144); // Set UV coordinates in order Top Left, Bottom Left, Top Right, Bottom Right RotVector.vx += 8; // Apply rotation on Z-axis. On PSX, the Z-axis is pointing away from the screen. addPrim(ot[db], poly); // add poly to the Ordering table nextpri += sizeof(POLY_GT4); // increment nextpri address with size of a POLY_F4 struct FntPrint("Hello textured shaded !"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_poly_gt_tw/Makefile ================================================ TARGET = hello_poly_gt_tw SRCS = hello_poly_gt_tw.c \ ../TIM/bousai.tim \ include ../common.mk ================================================ FILE: hello_poly_gt_tw/hello_poly_gt_tw.c ================================================ // Draw a textured poly primitive with gouraud_shading, using a repeating texture pattern // // With help from Nicolas Noble, Jaby smoll Seamonstah, Lameguy64 // // From ../psyq/addons/graphics/MESH/RMESH/TUTO0.C : // Schnappy 2021 #include #include #include #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 // Screen height #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 32 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 5 // Text Field Height #define OTLEN 8 // Ordering Table Length DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] short db = 0; // index of which buffer is used, values 0, 1 // 16bpp TIM extern unsigned long _binary____TIM_bousai_tim_start[]; extern unsigned long _binary____TIM_bousai_tim_end[]; extern unsigned long _binary____TIM_bousai_tim_length; TIM_IMAGE bousai; void LoadTexture(u_long * tim, TIM_IMAGE * tparam){ // This part is from Lameguy64's tutorial series : lameguy64.net/svn/pstutorials/chapter1/3-textures.html login/pw: annoyingmous OpenTIM(tim); // Open the tim binary data, feed it the address of the data in memory ReadTIM(tparam); // This read the header of the TIM data and sets the corresponding members of the TIM_IMAGE structure LoadImage(tparam->prect, tparam->paddr); // Transfer the data from memory to VRAM at position prect.x, prect.y DrawSync(0); // Wait for the drawing to end if (tparam->mode & 0x8){ // check 4th bit // If 4th bit == 1, TIM has a CLUT LoadImage(tparam->crect, tparam->caddr); // Load it to VRAM at position crect.x, crect.y DrawSync(0); // Wait for drawing to end } } void init(void) { ResetGraph(0); // Initialize and setup the GTE InitGeom(); SetGeomOffset(CENTERX,CENTERY); SetGeomScreen(CENTERX); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], 128, 128, 128); setRGB0(&draw[1], 128, 128, 128); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } int main(void) { int i; POLY_GT4 *poly = {0}; // pointer to a POLY_G4 SVECTOR RotVector = {0, 0, 0}; // Initialize rotation vector {x, y, z} VECTOR MovVector = {0, 0, 120, 0}; // Initialize translation vector {x, y, z} SVECTOR VertPos[4] = { // Set initial vertices position relative to 0,0 - see here : https://psx.arthus.net/docs/poly_f4.jpg {-32, -32, 0 }, // Vert 1 {-32, 32, 0 }, // Vert 2 { 32, -32, 0 }, // Vert 3 { 32, 32, 0 } // Vert 4 }; MATRIX PolyMatrix = {0}; DR_TPAGE * bousai_tpage; long polydepth; long polyflag; // Texture window DR_MODE * dr_mode; // Pointer to dr_mode prim RECT tws = {64, 32, 32, 32}; // Texture window coordinates : x, y, w, h // See libref47.pdf, p.242, 7-6, table 7-2 for possible values init(); LoadTexture(_binary____TIM_bousai_tim_start, &bousai); while (1) { ClearOTagR(ot[db], OTLEN); poly = (POLY_GT4 *)nextpri; // Set poly to point to the address of the next primitiv in the buffer // Set transform matrices for this polygon RotMatrix(&RotVector, &PolyMatrix); // Apply rotation matrix TransMatrix(&PolyMatrix, &MovVector); // Apply translation matrix SetRotMatrix(&PolyMatrix); // Set default rotation matrix SetTransMatrix(&PolyMatrix); // Set default transformation matrix setPolyGT4(poly); // Initialize poly as a POLY_F4 poly->tpage = getTPage(bousai.mode&0x3, 0, bousai.prect->x, bousai.prect->y); setRGB0(poly, 128, 128, 128); // Set vertice 1 color setRGB1(poly, 255, 0, 0); // Set vertice 2 color setRGB2(poly, 0, 255, 0); // Set vertice 3 color setRGB3(poly, 0, 0, 255); // Set vertice 4 color RotTransPers4( &VertPos[0], &VertPos[1], &VertPos[2], &VertPos[3], (long*)&poly->x0, (long*)&poly->x1, (long*)&poly->x2, (long*)&poly->x3, &polydepth, &polyflag ); // Perform coordinate and perspective transformation for 4 vertices setUV4(poly, 0, 0, 0, 144, 144, 0, 144, 144); // Set UV coordinates in order Top Left, Bottom Left, Top Right, Bottom Right RotVector.vy += 14; // Apply rotation on Z-axis. On PSX, the Z-axis is pointing away from the screen. addPrim(ot[db], poly); // add poly to the Ordering table nextpri += sizeof(POLY_GT4); // increment nextpri address with size of a POLY_GT4 struct // drawing mode primitive dr_mode = (DR_MODE *)nextpri; // initialize drawing mode primitive setDrawMode(dr_mode, 1, 0, getTPage(bousai.mode&0x3, 0, bousai.prect->x, bousai.prect->y), &tws); //set texture window addPrim(ot[db], dr_mode); nextpri += sizeof(DR_MODE); // increment nextpri address with size of a DR_MODE struct FntPrint("Hello textured shaded !"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_poly_inline/Makefile ================================================ TARGET = hello_poly_inline SRCS = hello_poly_inline.c \ include ../common.mk ================================================ FILE: hello_poly_inline/hello_poly_inline.c ================================================ // Hello poly ! Inline / DMPSX version // // Ref : /psyq/DOCS/Devrefs/Inlinref.pdf, p.18 // https://psx-spx.consoledev.net/geometrytransformationenginegte/ // PSX / Z+ // screen / //coordinate +-----X+ //system / | // eye | Y+ // // Credits, thanks : Nicolas Noble, Sickle, Lameguy64 @ psxdev discord : https://discord.com/invite/N2mmwp // https://discord.com/channels/642647820683444236/663664210525290507/834831466100949002 // Schnappy 07-2021 #include #include #include #include #include // OldWorld PsyQ has a inline_c.h file for inline GTE functions. We have to use the one at https://github.com/grumpycoders/pcsx-redux/blob/07f9b02d1dbb68f57a9f5b9773041813c55a4913/src/mips/psyq/include/inline_n.h // because the real GTE commands are needed in nugget : https://psx-spx.consoledev.net/geometrytransformationenginegte/#gte-coordinate-calculation-commands #include //~ #include // gtemac contains macro versions of the libgte functions, worth checking out to see the operations order. #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 + (VMODE << 4) // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256 #define CENTERX ( SCREENXRES >> 1 ) // Center of screen on x #define CENTERY ( SCREENYRES >> 1 ) // Center of screen on y #define MARGINX 0 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 7 // Text Field Height #define OTLEN 10 // Ordering Table Length DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] short db = 0; // index of which buffer is used, values 0, 1 void init(void) { ResetGraph(0); // Initialize and setup the GTE InitGeom(); //~ SetGeomOffset(CENTERX,CENTERY); gte_SetGeomOffset(CENTERX,CENTERY); gte_SetGeomScreen(CENTERX); // Set display environment SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw environment SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); // If PAL, use 320x256, hence 256 - 240 = 16 / 2 = 8 px vertical offset if (VMODE) SetVideoMode(MODE_PAL); SetDispMask(1); // Set background color setRGB0(&draw[0], 50, 50, 50); setRGB0(&draw[1], 50, 50, 50); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { // Wait for drawing DrawSync(0); // Wait for vsync VSync(0); // Flip DISP and DRAW env PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); // Flip db index db = !db; // Get next primitive in buffer nextpri = primbuff[db]; } int main(void) { long p, flag, OTz; SVECTOR rotVector, rotVector4 = {0}; // Initialize rotation vector {x, y, z} VECTOR transVector = {0, 0, CENTERX, 0}; // Initialize translation vector {x, y, z} SVECTOR vertPos[4] = { { 0, -32, 0, 0 }, // Vert 1 { 32, 0, 0, 0 }, // Vert 2 { -32, 0, 0, 0 }, { 0, 32, 0, 0 } }; // Vert 3 MATRIX workMatrix = {0}; POLY_F3 * poly = {0}; // pointer to a POLY_F4 POLY_F4 * poly4 = {0}; // pointer to a POLY_F4 init(); while (1) { // Set Ordering table ClearOTagR(ot[db], OTLEN); // Draw on the left part of the screen transVector.vx = -CENTERX/2; // Increment rotation angle on Y axis rotVector.vy += 8; rotVector.vx -= 4 ; // Find rotation matrix from vector, store in RotMatrix_gte(&rotVector, &workMatrix); // Ditto for translation TransMatrix(&workMatrix, &transVector); // Set the matrices we just found gte_SetRotMatrix(&workMatrix); gte_SetTransMatrix(&workMatrix); // Cast next primitive in buffer as a POLY_F4 (see display() ) poly = (POLY_F3 *)nextpri; // Draw a Tri // Initialize poly as a POLY_F3 setPolyF3(poly); // Set poly color - Hot pink setRGB0(poly, 255, 0, 255); // Store vertex positions for current polygon in registers v0,v1,v2 // Can be replaced by one gte_ldv3 call : // gte_ldv3(&vertPos[0], &vertPos[1], &vertPos[2]); gte_ldv0(&vertPos[0]); gte_ldv1(&vertPos[1]); gte_ldv2(&vertPos[2]); // RotTransPers3 : Perform coordinate and perspective transformation for three vertices. // Use gte_rtps() for one vertex. gte_rtpt(); // Get screen coordinates from cop2 registers XY0,XY1,XY2 and store them in primitive's x0, y0, x1, y1, x2, y2 members. // Can be replace with one gte_stsxy3() call : // gte_stsxy3(&poly->x0, &poly->x1, &poly->x2); // Can also be replaced with a primitive type dependant version : // gte_stsxy3_f3(poly); gte_stsxy0(&poly->x0); gte_stsxy1(&poly->x1); gte_stsxy2(&poly->x2); // Get depth interpolation coefficient p gte_stdp(&p); // Get the flag - see libover47.pdf, p.143 for details on ppossible values gte_stflg(&flag); // Get screen coordinate Z/4 gte_stszotz(&OTz); // GTE macro version - needs 'gtemac.h' to be included - uncomment l.21 //~ gte_RotTransPers3( &VertPos[0], &VertPos[1], &VertPos[2], //~ &poly->x0, &poly->x1, &poly->x2, //~ &p, &flag, &OTz ); // add poly to the Ordering table addPrim(ot[db], poly); // increment nextpri address with size of a POLY_F3 struct nextpri += sizeof(POLY_F3); // Draw a Quad // // The GTE rtpt can only transform 3 vertices at a time, so we have to do all operations as 3 + 1. // Move to right of screen transVector.vx = CENTERX/2; // Increment rot on X/Y axis rotVector4.vy -= 8 ; rotVector4.vx -= 4 ; // Set matrices RotMatrix_gte(&rotVector4, &workMatrix); TransMatrix(&workMatrix, &transVector); gte_SetRotMatrix(&workMatrix); gte_SetTransMatrix(&workMatrix); // Cast a POLY_F4 at the address we just incremented. poly4 = (POLY_F4 *)nextpri; // Initialize poly as a POLY_F4 setPolyF4(poly4); // Set Poly color - Blue setRGB0(poly4, 0, 255, 255); // Transform 3 first vertices gte_ldv3(&vertPos[0], &vertPos[1], &vertPos[2]); gte_rtpt(); gte_stsxy3_f4(poly4); // Transform remaining vertex gte_ldv0(&vertPos[3]); gte_rtps(); // SXY3 is set with gte_stsxy() or gte_stsxy2() ¯\_(ツ)_/¯ gte_stsxy(&poly4->x3); // Get p, flag and OTz gte_stdp(&p); gte_stflg(&flag); gte_stszotz(&OTz); addPrim(ot[db], poly4); // add poly to the Ordering table nextpri += sizeof(POLY_F4); // increment nextpri address with size of a POLY_F3 struct // Display text FntPrint("Hello Inline GTE !\n"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_poly_stp/Makefile ================================================ TARGET = hello_poly_stp SRCS = hello_poly_stp.c \ TIM/stpOnAlpha.tim \ TIM/stpOnAlphaI.tim \ TIM/stpOnBlack.tim \ TIM/stpOnColIndex.tim \ TIM/stpOnNonBlack.tim \ include ../common.mk ================================================ FILE: hello_poly_stp/README.md ================================================ ![Hello_stp](https://wiki.arthus.net/assets/hello-stp.png) # STP : Semi-Transparency usage This example shows the various way of converting an image with transparency to a TIM and use it in code. It also shows the effect of activating Semi-Transparency on a primitive textured with those images. Use the `SELECT` button to switch primitive semi-transparency on and off. It also features a few C struct to facilitate access to the TIM file / pixel data. See [https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/STP](https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/STP) for more information on how to convert your images to TIM while preserving transparency. See [https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/TIM#transparency](https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/TIM#transparency) for mor details on transparency. ================================================ FILE: hello_poly_stp/hello_poly_stp.c ================================================ // Demo the different settings for pixel and primitive semi-transparency // // Schnappy 07-2021 #include #include #include #include #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 // Screen height #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 16 // margins for text display #define MARGINY 16 #define OTLEN 8 // Ordering Table Length DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] short db = 0; // index of which buffer is used, values 0, 1 // RGB pixels are 16bpp, 5b Red, 5b Green, 5b Blue, 1b STP (semi-transparency) // See http://psx.arthus.net/sdk/Psy-Q/DOCS/FileFormat47.pdf, p.183 typedef struct RGB_PIX { u_int R:5, G:5, B:5, STP:1; } RGB_PIX; // TIM's pixel data // See http://psx.arthus.net/sdk/Psy-Q/DOCS/FileFormat47.pdf, p.182 typedef struct PIXEL { u_long bnum; u_short DX, DY; u_short W, H; RGB_PIX data[]; } PIXEL; // TIM's CLUT section - exists only in 4/8bpp TIMs // See See http://psx.arthus.net/sdk/Psy-Q/DOCS/FileFormat47.pdf, p.181 typedef struct CLUT { u_long bnum; u_short DX, DY; u_short W, H; u_short clut[]; } CLUT; // 4/8bpp TIM files have CLUT typedef struct TIM_FILE_CLUT{ u_long ID; u_long flag; u_long clut; PIXEL pixel[]; } TIM_FILE_CLUT; // 16/24bpp TIM files have not CLUT member // See See http://psx.arthus.net/sdk/Psy-Q/DOCS/FileFormat47.pdf, p.179 typedef struct TIM_FILE{ u_long ID; u_long flag; PIXEL pixel[]; } TIM_FILE; // If we were using C++, we could use templates //~ struct EmbeddedClut { u_long clut; }; //~ struct NoEmbeddedClut { }; //~ template //~ struct TIM_FILE { //~ u_long ID; //~ u_long flag; //~ std::conditional clut; //~ PIXEL pixel[]; //~ }; // 16bpp TIM // STP set on black pixels ( STP, B, R, G == 1, 0, 0 ,0) extern TIM_FILE _binary_TIM_stpOnBlack_tim_start; // STP set on non black pixels ( STP, B, R, G == 1, !0, !0 ,!0) extern TIM_FILE _binary_TIM_stpOnNonBlack_tim_start; // STP set on image's alpha channnel ( STP, B, R, G == 1, a, a ,a) extern TIM_FILE _binary_TIM_stpOnAlphaI_tim_start; // STP set on 8bpp TIM's CLUT index 0 ( STP, B, R, G == 1, i, i, i) extern TIM_FILE _binary_TIM_stpOnColIndex_tim_start; // Store in an array so we can iterate over it TIM_FILE * timFiles[4]; TIM_IMAGE timImages[4]; // Number of primitives to draw #define NUM_PRIM 4 // Primitive stp flag : 0 == off, 1 == on char stpFlag = 0; void LoadTexture(TIM_FILE * tim, TIM_IMAGE * tparam){ // This part is from Lameguy64's tutorial series : lameguy64.net/svn/pstutorials/chapter1/3-textures.html login/pw: annoyingmous OpenTIM( ( u_long * ) tim); // Open the tim binary data, feed it the address of the data in memory ReadTIM(tparam); // This read the header of the TIM data and sets the corresponding members of the TIM_IMAGE structure LoadImage(tparam->prect, tparam->paddr); // Transfer the data from memory to VRAM at position prect.x, prect.y DrawSync(0); // Wait for the drawing to end if (tparam->mode & 0x8){ // check 4th bit // If 4th bit == 1, TIM has a CLUT LoadImage(tparam->crect, tparam->caddr); // Load it to VRAM at position crect.x, crect.y DrawSync(0); // Wait for drawing to end } } void init(void) { ResetGraph(0); // Initialize and setup the GTE InitGeom(); SetGeomOffset( 0 , 0 ); SetGeomScreen( CENTERX ); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], 255, 0, 128); setRGB0(&draw[1], 255, 0, 128); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, MARGINY, SCREENXRES - MARGINX * 2, SCREENXRES - MARGINY * 2, 0, 512 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } int main(void) { // Populate array with pointers to TIM data timFiles[0] = &_binary_TIM_stpOnBlack_tim_start; timFiles[1] = &_binary_TIM_stpOnNonBlack_tim_start; timFiles[2] = &_binary_TIM_stpOnAlphaI_tim_start; timFiles[3] = &_binary_TIM_stpOnColIndex_tim_start; // Init Disp/Draw, double buffer, font init(); // Init proto pad PadInit(0); int pad, oldPad; // Pointer to a POLY_G4 POLY_FT4 * poly[4] = {0}; SVECTOR VertPos[4] = { // Set initial vertices position relative to 0,0 - see here : https://psx.arthus.net/docs/poly_f4.jpg {-32, -32, 1 }, // Vert 1 {-32, 32, 1 }, // Vert 2 { 32, -32, 1 }, // Vert 3 { 32, 32, 1 } // Vert 4 }; VECTOR transVector = { SCREENXRES/3, SCREENYRES/4, 128, 0}; // Initialize translation vector {x, y, z, pad} SVECTOR rotVector = {0}; // Initialize rotation vector {x, y, z} // Load textures to VRAM for (char tim = 0; tim < NUM_PRIM; tim++){ LoadTexture(timFiles[tim], &timImages[tim]); } while (1) { MATRIX Work; // Clear OT ClearOTagR(ot[db], OTLEN); // Use a temporary work matrix // Set Trans/Rot vectors to work matrix RotMatrix(&rotVector, &Work); // Apply rotation matrix TransMatrix(&Work, &transVector); // Apply translation matrix SetRotMatrix(&Work); // Set default rotation matrix SetTransMatrix(&Work); // Set default transformation matrix // Draw NUM_PRIM primitives for (int i = 0; i < NUM_PRIM; i++){ long p, flag; // Draw prims with an offset base on iteration number transVector.vx = SCREENXRES/NUM_PRIM + (i * (SCREENXRES/NUM_PRIM + 32) ) ; transVector.vy = SCREENYRES/NUM_PRIM; if ( i >= 2) { transVector.vx = SCREENXRES/NUM_PRIM + ((i - 2) * (SCREENXRES/NUM_PRIM + 32) ) ; transVector.vy = SCREENYRES/2 + 24; } TransMatrix(&Work, &transVector); SetTransMatrix(&Work); // Set poly poly[i] = (POLY_FT4 *)nextpri; // Set poly to point to the address of the next primitiv in the buffer setPolyFT4(poly[i]); // Initialize poly as a POLY_F4 // Get texture page poly[i]->tpage = getTPage( timImages[i].mode & 0x3, 0, // Get Tpage coordinates from the TIM_IMAGE mode and prect members. timImages[i].prect->x, timImages[i].prect->y); // If 8/4bpp, get CLUT if ( (timImages[i].mode & 0x3) < 2 ) { setClut(poly[i], timImages[i].crect->x, timImages[i].crect->y ); } setRGB0(poly[i], 128, 128, 128); // Set poly color (neutra here) SetSemiTrans(poly[i], stpFlag); RotTransPers4( &VertPos[0], &VertPos[1], &VertPos[2], &VertPos[3], (long*)&poly[i]->x0, (long*)&poly[i]->x1, (long*)&poly[i]->x2, (long*)&poly[i]->x3, &p, &flag ); // Perform coordinate and perspective transformation for 4 vertices setUV4(poly[i], 0, 0, 0, 144, 144, 0, 144, 144); // Set UV coordinates in order Top Left, Bottom Left, Top Right, Bottom Right // Add poly to the Ordering table addPrim(ot[db], poly[i]); // Increment nextpri address with size of a POLY_F4 struct nextpri += sizeof(POLY_FT4); } // Get pad input pad = PadRead(0); // If select button is used if ( pad & PADselect && !( pad & oldPad ) ){ // Flip STP flag stpFlag = !stpFlag; // Set flag to avoir misfire oldPad = pad; } // Reset flag when button released if (!(pad & PADselect)) { oldPad = 0; } FntPrint("Hello semi-transparency !\nPrim STP (push Select) : %d\n\n\n\n\n\n\n\n\n\n\n\n", stpFlag); FntPrint(" stp on black stp on non-black\n\n\n\n\n\n\n\n\n\n\n\n"); FntPrint(" stp on non-black stp on col index"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_sio/Makefile ================================================ TARGET = hello_sio SRCS = hello_sio.c \ include ../common.mk ================================================ FILE: hello_sio/hello_sio.c ================================================ // hello_sio example // This example will display the RX data in a 64 char rolling buffer. // Use minicom or any other serial comm program and a serial/USB cable. // // Relevant doc is libref47.pdf, l.1120-1127 // Schnappy - 04/2021 // // Based on : ../psyq/psx/sample/serial/SIO // sio echo back // 1.00 Jan.28.1997 shino #include #include #include #include #include // Needed for SIO operations #include // Needed for manipulating strings #include // Display stuff (see hello_tile for the basics) #define VMODE 0 #define SCREENXRES 320 #define SCREENYRES 240 #define CENTERX SCREENXRES/2 #define CENTERY SCREENYRES/2 #define OTLEN 2048 // Maximum number of OT entries #define PRIMBUFFLEN 32768 // Maximum number of POLY_GT3 primitives // Display and draw environments, double buffered DISPENV disp[2]; DRAWENV draw[2]; u_long ot[2][OTLEN]; // Ordering table (contains addresses to primitives) char primbuff[2][PRIMBUFFLEN]; // Primitive list // That's our prim buffer char * nextpri = primbuff[0]; // Primitive counter short db = 0; // Current buffer counter // SIO #define MAX_CHARS 64 u_char SIO = 1; // Is SIO enabled ? u_char SIOinit = 0; // Is SIO initialized ? // Prototypes void init(void); void display(void); void init(){ // Reset the GPU before doing anything and the controller ResetGraph(0); // Set the display and draw environments SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); // If in PAL mode, add vertical offset if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); setRGB0(&draw[0], 0, 0, 255); setRGB0(&draw[1], 0, 0, 255); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); // Init font system FntLoad(960, 0); FntOpen(16, 16, 196, 64, 0, 256); } void display(void){ DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); db = !db; } int main() { init(); // Main loop while (1) { // Buffer for the RX data of size MAX_CHARS static char buffer[ MAX_CHARS ] = {0}; // If SIO flag is set, initialize and get data if( SIO ){ // Is SIO is not initialized, dot it if( ! SIOinit ){ ResetCallback(); // Load SIO driver at 115200bps AddSIO(115200); ResetGraph(0); // Use _sio_control to clear driver status error-related bits // See psyq's libref47.pdf, p.1125 for the commands and status tables _sio_control(2,1,0); SIOinit = 1; } // Limit input buffer to MAX_CHARS chars, making it a rolling buffer if( strlen(buffer) > MAX_CHARS ){ // If that limit is reached, remove first char in string memmove(buffer, buffer + 1, strlen(buffer)); } // Check if sio driver is able to write communications data // If so, this means reading is not occuring if( _sio_control(0,0,0) & SR_RXRDY ){ // SR_RXRDY == 0x2 // Read byte char c = _sio_control(0,4,0); // Add to buffer strncat(buffer, &c, 1); } } // END SIO FUN FntPrint("Hello Serial!\n\n"); if( buffer ){ FntPrint("%s", buffer); } FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_sprt/Makefile ================================================ TARGET = hello_sprt SRCS = hello_sprt.c \ ../TIM/TIM16.tim \ ../TIM/TIM8.tim \ ../TIM/TIM4.tim \ include ../common.mk ================================================ FILE: hello_sprt/hello_sprt.c ================================================ // Draw a textured sprt primitive // // based on Lameguy64's tutorial : http://lameguy64.net/svn/pstutorials/chapter1/3-textures.html // Schnappy 2020 #include #include #include #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 #define SCREENYRES 240 #define CENTERX SCREENXRES/2 #define CENTERY SCREENYRES/2 #define MARGINX 32 // margins for text display #define MARGINY 44 #define FONTSIZE 8 * 3 // Text Field Height #define OTLEN 8 // Ordering Table Length DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] short db = 0; // index of which buffer is used, values 0, 1 // Embed TIM files // See https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/(Archived)--Embedding-binary-data-in-your-psx-executable // 16bpp TIM extern unsigned long _binary____TIM_TIM16_tim_start[]; extern unsigned long _binary____TIM_TIM16_tim_end[]; extern unsigned long _binary____TIM_TIM16_tim_length; // 8bpp TIM extern unsigned long _binary____TIM_TIM8_tim_start[]; extern unsigned long _binary____TIM_TIM8_tim_end[]; extern unsigned long _binary____TIM_TIM8_TIM_length; // 4bpp TIM extern unsigned long _binary____TIM_TIM4_tim_start[]; extern unsigned long _binary____TIM_TIM4_tim_end[]; extern unsigned long _binary____TIM_TIM4_tim_length; TIM_IMAGE TIM_16; TIM_IMAGE TIM_8; TIM_IMAGE TIM_4; void LoadTexture(u_long * tim, TIM_IMAGE * tparam){ // This part is from Lameguy64's tutorial series : lameguy64.net/svn/pstutorials/chapter1/3-textures.html login/pw: annoyingmous OpenTIM(tim); // Open the tim binary data, feed it the address of the data in memory ReadTIM(tparam); // This read the header of the TIM data and sets the corresponding members of the TIM_IMAGE structure LoadImage(tparam->prect, tparam->paddr); // Transfer the data from memory to VRAM at position prect.x, prect.y DrawSync(0); // Wait for the drawing to end if (tparam->mode & 0x8){ // check 4th bit // If 4th bit == 1, TIM has a CLUT LoadImage(tparam->crect, tparam->caddr); // Load it to VRAM at position crect.x, crect.y DrawSync(0); // Wait for drawing to end } } void init(void) { ResetGraph(0); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], 50, 50, 50); setRGB0(&draw[1], 50, 50, 50); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } int main(void) { SPRT * sprt_16b; // Define 3 pointers to SPRT struct SPRT * sprt_8b; SPRT * sprt_4b; DR_TPAGE * tpage_16b; // Define 3 pointers to DR_TPAGE struct. We need three because our images are on three DR_TPAGE * tpage_8b; // different texture pages. DR_TPAGE * tpage_4b; init(); LoadTexture(_binary____TIM_TIM16_tim_start, &TIM_16); // Load everything to vram LoadTexture(_binary____TIM_TIM8_tim_start, &TIM_8); LoadTexture(_binary____TIM_TIM4_tim_start, &TIM_4); while (1) { ClearOTagR(ot[db], OTLEN); // Loading a 16 bit TIM sprt_16b = (SPRT *)nextpri; // Cast whats at nexpri as a SPRT named sprt_16b setSprt(sprt_16b); // Initialize the SPRT struct setRGB0(sprt_16b, 128, 128, 128); // Set RGB color. 128,128,128 is neutral. You can color the image by adjusting these values setXY0(sprt_16b, 28, MARGINY); // Set sprite position setWH(sprt_16b, 64, 128 ); // Set sprite width and height addPrim(ot[db], sprt_16b); // add the sprite primitive to the ordering table nextpri += sizeof(SPRT); // increment nextpri so that it points just after sprt_16b in the primitive buffer // Set Texture page for the 16bit tim : 768, 0 - No CLUT // Note : You need to use setDrawTPage each time you want to use a texture that's on a different texture page tpage_16b = (DR_TPAGE*)nextpri; setDrawTPage(tpage_16b, 0, 1, // Set the Texture Page the texture we want resides on. getTPage(TIM_16.mode&0x3, 0, // Here we are using bitmasking to deduce the picture mode : &0x3 TIM_16.prect->x, TIM_16.prect->y)); // In binary, 3 is 11, so we only keep the first two bits // Values can be 00 (0), 01 (1), 10(2), respectively, 4bpp, 8bpp, 15bpp, 24bpp. See Fileformat47.pdf, p.180 // Similarly, we could use bitmasking to deduce if there is a CLUT by bitmasking the 4th bit : if(TIM_IMAGE.mode & 0x8) LoadImage... : addPrim(ot[db], tpage_16b); // add the sprite primitive to the ordering table nextpri += sizeof(DR_TPAGE); // Advance next primitive address // Loading a 8 bit TIM sprt_8b = (SPRT *)nextpri; setSprt(sprt_8b); setRGB0(sprt_8b, 128, 128, 128); setXY0(sprt_8b, sprt_16b->x0 + sprt_16b->w + 32, MARGINY); setWH(sprt_8b, 64, 128 ); setClut(sprt_8b, TIM_8.crect->x, TIM_8.crect->y); // Only difference here is we set the CLUT to the position of the VRAM we loaded the palette earlier (see LoadTexture()) addPrim(ot[db], sprt_8b); nextpri += sizeof(SPRT); // Set Texture page for the 8bit tim : 512, 256 - CLUT is at 0, 480 tpage_8b = (DR_TPAGE*)nextpri; setDrawTPage(tpage_8b, 0, 1, getTPage(TIM_8.mode&0x3, 0, TIM_8.prect->x, TIM_8.prect->y)); addPrim(ot[db], tpage_8b); nextpri += sizeof(DR_TPAGE); // Loading a 4 bit TIM sprt_4b = (SPRT *)nextpri; setSprt(sprt_4b); setRGB0(sprt_4b, 128, 128, 128); setXY0(sprt_4b, sprt_8b->x0 + sprt_8b->w + 32, MARGINY); setWH(sprt_4b, 64, 128 ); setClut(sprt_4b, TIM_4.crect->x, TIM_4.crect->y); addPrim(ot[db], sprt_4b); nextpri += sizeof(SPRT); // Set Texture page for the 4bit tim : 512, 256 - CLUT is at 0, 480 tpage_4b = (DR_TPAGE*)nextpri; setDrawTPage(tpage_4b, 0, 1, getTPage(TIM_4.mode&0x3, 0, TIM_4.prect->x, TIM_4.prect->y)); addPrim(ot[db], tpage_4b); nextpri += sizeof(DR_TPAGE); FntPrint("16 Bit! "); FntPrint("8 Bit! "); FntPrint("4 Bit!\n\n"); FntPrint("Check VRAM in emu to see the dif"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_spu_readback/Makefile ================================================ .PHONY: all cleansub all: mkpsxiso -y ./isoconfig.xml cleansub: $(MAKE) clean rm -f hello_spu_readback.cue hello_spu_readback.bin TARGET = hello_spu_readback SRCS = hello_spu_readback.c \ include ../common.mk ================================================ FILE: hello_spu_readback/README.md ================================================ This example is adapted from PsyQ's sample : `psyq/psx/sample/sound/CDVOL, main.c,v 1.14 1997/05/02 13:05:21 by ayako`. It was edited to fix typos, have a hopefully better code organization with hopefully more usefull variable names. What it does is demonstrate how to transfer data from the PSX 's SPU to main memory in order to analyze / process the audio signal and dostuff accordingly. In this instance, it's used to determine the coordinates of a few primitives to display a [VU-meter](https://en.wikipedia.org/wiki/VU_meter). This technique is known to be used in certain games for lipsynching or audio visualization ( Crash team racing, Hercules, Vib Ribbon ...). ## pcsx-redux : no animation Pcsx-redux does not yet support these specific SPU buffers nor triggerring IRQ from them so as of 11-2021 this example doesn't work in this particular emulator. If looking for an alternative, check [duckstation](https://www.duckstation.org/) out. ## PsyQ's SpuReadDecodedData() doc errata The main function for transferring data from the SPU to the RAM is `SpuReadDecodedData()`, and is documented in **LibRef47.pdf, p1054**. The table on this page (Table 15-2) contains erroneous data and should read : ![Spu addresses range](https://wiki.arthus.net/assets/spureaddecodeddata_errata.png) The correct address ranges for the SPU buffer is : | Map (bytes) | Data contents | |-------------|---------------| | 0x000 - 0x3ff | CD Left channel | | 0x400 - 0x7ff | CD Right channel | | 0x800 - 0xbff | Voice 1 | | 0xC00 - 0xfff | Voice 3 | ## Compiling You need [mkpsxiso](https://github.com/Lameguy64/mkpsxiso) in your $PATH to generate a PSX disk image. Typing ```bash make ``` in a terminal will compile and generate the bin/cue files. Typing ```bash make cleansub ``` will clean the current directory ## More on CDDA See the [hello_cdda](https://github.com/ABelliqueux/nolibgs_hello_worlds/tree/main/hello_cdda) example in this repo. ## Docs and links Original psyq example : `psyq/psx/sample/sound/CDVOL, main.c,v 1.14 1997/05/02 13:05:21 by ayako` ## Music credits Track 1 : Beach Party by Kevin MacLeod Link: https://incompetech.filmmusic.io/song/3429-beach-party License: https://filmmusic.io/standard-license Track 2: Funk Game Loop by Kevin MacLeod Link: https://incompetech.filmmusic.io/song/3787-funk-game-loop License: https://filmmusic.io/standard-license ================================================ FILE: hello_spu_readback/hello_spu_readback.c ================================================ // SPU readback example // adapted from PsyQ's sample : psyq/psx/sample/sound/CDVOL, main.c,v 1.14 1997/05/02 13:05:21 by ayako // Schnappy 11-2021 #include #include #include #include #include #include #include // CD library #include // SPU library #include #include "../thirdparty/nugget/common/syscalls/syscalls.h" #define printf ramsyscall_printf #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 + (VMODE << 4) // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256 #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 0 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 7 // Text Field Height #define OTLEN 8 // Ordering Table Length // Number of bars #define BARNUM 2 // Peak cursor width #define TSIZE 10 // Bar size / 2 #define BSIZE 128 // Top Y coordinate of Left volume bar #define BARTOP 100 // Bottom Y coordinates of Left volume bar #define BARBOTTOM ((BARTOP)+5) // Vertical spacing #define MARGIN 40 // Bars left coordinates #define MINBAR CENTERX - BSIZE // Bars right coordinates #define MAXBAR ( CENTERX + BSIZE + TSIZE ) // Bars IDs #define LEFTBAR 0 #define RIGHTBAR 1 // Return absolute value of a number #define ABS(x) (((x)<0)?(-(x)):(x)) DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes uint8_t primbuff[2][32768]; // double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes uint8_t *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] uint8_t db = 0; // index of which buffer is used, values 0, 1 // SPU attributes SpuCommonAttr spuSettings; // SPU IRQ address uint16_t SpuIrqAddr; // SPU decoded data buffer SpuDecodedData decodedData; // CD volume: current sample's max values ulong leftMax, rightMax; // Last 2 seconds's peak volume values ulong leftPeak, rightPeak; // Primitives for drawing the VU-metre, double buffered // Blue : background bar POLY_F4 * bar[BARNUM]; // White : current value POLY_F4 * current[BARNUM]; // Red : volume peak in the last 3 seconds POLY_F4 * peak[BARNUM]; // Colors for the VU-metre CVECTOR bg = {20, 10, 0}; CVECTOR fg = {10,200,20}; CVECTOR cursor = {200,40,10}; void init(void) { ResetGraph(0); // Initialize drawing engine with a complete reset (0) InitGeom(); SetGeomOffset(CENTERX,CENTERY); SetGeomScreen(CENTERX); SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1] SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1] SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1] SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1] // Set video mode if (VMODE){ SetVideoMode(MODE_PAL);} SetDispMask(1); // Display on screen setRGB0(&draw[0], 50, 50, 50); // set color for first draw area setRGB0(&draw[1], 50, 50, 50); // set color for second draw area draw[0].isbg = 1; // set mask for draw areas. 1 means repainting the area with the RGB color each frame draw[1].isbg = 1; PutDispEnv(&disp[db]); // set the disp and draw environnments PutDrawEnv(&draw[db]); FntLoad(960, 0); // Load font to vram at 960,0(+128) // Top bar FntOpen (MINBAR, BARTOP - 10, 200, 150, 0, 64); // Bottom bar FntOpen (MINBAR, BARTOP - 10 + (MARGIN), 200, 150, 0, 64); // Debug FntOpen (32, SCREENYRES - 74, SCREENXRES - 64, 64, 0, 200); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); DrawOTag(&ot[db][OTLEN - 1]); db = !db; nextpri = primbuff[db]; } void initPrimitives(void) { // Set primitives from primbuff[] bar[0] = (POLY_F4 *)nextpri; bar[1] = (POLY_F4 *)nextpri + sizeof(POLY_F4); current[0] = (POLY_F4 *)nextpri + (sizeof(POLY_F4) * 2); current[1] = (POLY_F4 *)nextpri + (sizeof(POLY_F4) * 3); peak[0] = (POLY_F4 *)nextpri + (sizeof(POLY_F4) * 4); peak[1] = (POLY_F4 *)nextpri + (sizeof(POLY_F4) * 5); // Set each primitive to their default settings for (int i = 0; i < BARNUM; i++) { // Volume bar background is blue SetPolyF4 ( bar[i] ); setRGB0 ( bar[i], bg.r,bg.g,bg.b ); setXY4 ( bar[i], // Top-left MINBAR, BARTOP + i * MARGIN, // Top-right MAXBAR, BARTOP + i * MARGIN, // Bottom-left MINBAR, BARBOTTOM + i * MARGIN, // Bottom-right MAXBAR, BARBOTTOM + i * MARGIN); // Current volume is light purple-ish SetPolyF4 (current[i]); setRGB0 ( current[i], fg.r,fg.g,fg.b); setXY4 ( current[i], MINBAR, BARTOP + i * MARGIN, MINBAR + TSIZE, BARTOP + i * MARGIN, MINBAR, BARBOTTOM + i * MARGIN, MINBAR + TSIZE, BARBOTTOM + i * MARGIN); // Initialize peak cursor SetPolyF4 ( peak[i] ); setRGB0 ( peak[i], cursor.r,cursor.g,cursor.b); setXY4 ( peak[i], MINBAR, BARTOP + i * MARGIN, MINBAR + TSIZE, BARTOP + i * MARGIN, MINBAR, BARBOTTOM + i * MARGIN, MINBAR + TSIZE, BARBOTTOM + i * MARGIN); } } // Unused - should be called whenever this madness needs to be ended void terminate(void) { // Turn SPU irq off SpuSetIRQ (SPU_OFF); // Clear callback functions SpuSetIRQCallback ((SpuIRQCallbackProc) NULL); SpuSetTransferCallback ((SpuTransferCallbackProc) NULL); // Reset SPU settings spuSettings.mask = (SPU_COMMON_MVOLL | SPU_COMMON_MVOLR | SPU_COMMON_CDVOLL | SPU_COMMON_CDVOLR | SPU_COMMON_CDMIX ); spuSettings.mvol.left = 0; spuSettings.mvol.right = 0; spuSettings.cd.volume.left = 0; spuSettings.cd.volume.right = 0; spuSettings.cd.mix = SPU_OFF; SpuSetCommonAttr (&spuSettings); // Stop CD CdStop (); // Stop SPU processing SpuQuit (); // Re-init display env ResetGraph (3); // stop callback processing StopCallback (); } // Print corresponding data for each volume bar void printDataInfo(void) { // We're using 2 streams FntPrint (0, "L: %04x peak/%04x\n", leftMax, leftPeak); FntPrint (1, "R: %04x peak/%04x\n", rightMax, rightPeak); FntFlush (0); FntFlush (1); } // SPU IRQ calback function void eachIRQ (void) { SpuSetIRQ (SPU_OFF); /**/ SpuReadDecodeData (&decodedData, SPU_CDONLY); /**/ } // DMA Transfer callback function void eachDMA (void) { if (SpuIrqAddr == 0x0) SpuIrqAddr = 0x200; else SpuIrqAddr = 0x0; // Change IRQ address SpuSetIRQAddr (SpuIrqAddr); // Turn SPU IRQ requests on SpuSetIRQ (SPU_ON); } void findSampleMaxVolume(void) { // Search maximum volume value of the SPU decoded data // SPU buffer data range adresses long dataLowerAdress, dataUpperAdress; // Current sample's max and working value short maxL = 0, tmpL; short maxR = 0, tmpR; // Timers for the Peak cursor, reset after 120 iterations. static long timeCursorL = 0, timeCursorR = 0; // Find SPU data range according to current half we're working on if (SpuIrqAddr == 0x0) { /* 1st part is available */ dataLowerAdress = 0x0; dataUpperAdress = 0x1ff; } else { /* 2nd part is available */ dataLowerAdress = 0x200; dataUpperAdress = 0x3ff; } // Examine and find max volume in the data range for (long i = dataLowerAdress; i < dataUpperAdress; i ++) { // Examine SPU decoded data tmpL = ABS(decodedData.cd_left[i]); tmpR = ABS(decodedData.cd_right[i]); // Only keep maximum value for this sample if (maxL < tmpL ) { maxL = tmpL ; } if (maxR < tmpR ) { maxR = tmpR; } } leftMax = (long) maxL; rightMax = (long) maxR; // Peak level if (leftPeak < leftMax) { leftPeak = leftMax; timeCursorL = 0; } if (rightPeak < rightMax) { rightPeak = rightMax; timeCursorR = 0; } // Peak cursors: hold 2s@60fps. // Increment counters until 120 is reached, then set cursors position to current leftMax/rightMax values if (timeCursorL < 120) { timeCursorL ++; } else { timeCursorL = 0; leftPeak = leftMax; } if (timeCursorR < 120) { timeCursorR ++; } else { timeCursorR = 0; rightPeak = rightMax; } } int main(void) { // Values used to switch CD track u_int counter = 0; int8_t flip = 1; // These will hold the normalised values of leftMax/rightMax, leftPeak/rightPeak long lMax, rMax, lPeak, rPeak; // Init display init(); // Init CD system CdInit (); // Init Spu SpuInit(); // Initialize SPU related variables leftMax = rightMax = 0; leftPeak = rightPeak = 0; // Fill SPU data buffers with 0s for (int i = 0; i < SPU_DECODEDDATA_SIZE; i ++) { decodedData.cd_left[i] = 0; decodedData.cd_right[i] = 0; } // SPU setup // Set master & CD volume to max spuSettings.mask = (SPU_COMMON_MVOLL | SPU_COMMON_MVOLR | SPU_COMMON_CDVOLL | SPU_COMMON_CDVOLR | SPU_COMMON_CDMIX); // Master volume should be in range 0x0000 - 0x3fff spuSettings.mvol.left = 0x3fff; spuSettings.mvol.right = 0x3fff; // Cd volume should be in range 0x0000 - 0x7fff spuSettings.cd.volume.left = 0x7fff; spuSettings.cd.volume.right = 0x7fff; // Enable CD input ON spuSettings.cd.mix = SPU_ON; // Apply settings SpuSetCommonAttr(&spuSettings); // Set transfer mode SpuSetTransferMode(SPU_TRANSFER_BY_DMA); // Callbacks setup // Set Transfer callback (void) SpuSetTransferCallback ((SpuTransferCallbackProc) eachDMA); // set IRQ callback SpuSetIRQCallback ((SpuIRQCallbackProc) eachIRQ); // Initialize SPU IRQ address SpuIrqAddr = 0x200; // Set IRQ address SpuSetIRQAddr (SpuIrqAddr); // Turn interrupt request ON SpuSetIRQ(SPU_ON); // CD Playback setup // Play second audio track // Get CD TOC CdlLOC loc[100]; int ntoc; while ((ntoc = CdGetToc(loc)) == 0) { /* Read TOC */ printf("No TOC found: please use CD-DA disc...\n"); FntPrint(2, "No TOC found: please use CD-DA disc...\n"); } // Prevent out of bound pos for (int i = 1; i < ntoc; i++) { CdIntToPos(CdPosToInt(&loc[i]) - 74, &loc[i]); } // Those array will hold the return values of the CD commands u_char param[4], result[8]; // Set CD parameters ; Report Mode ON, CD-DA ON. See LibeOver47.pdf, p.188 param[0] = CdlModeRept|CdlModeDA; // Set CD mode CdControlB (CdlSetmode, param, 0); // Wait 3 vsync VSync (3); // Play second track in toc array CdControlB (CdlPlay, (u_char *)&loc[3], 0); // Graphics setup initPrimitives(); while (1) { counter++; ClearOTagR(ot[db], OTLEN); // Normalize volume lMax = (leftMax * 256) / 0x8000 + MINBAR; rMax = (rightMax * 256) / 0x8000 + MINBAR; lPeak = (leftPeak * 256) / 0x8000 + MINBAR; rPeak = (rightPeak * 256) / 0x8000 + MINBAR; // Update primitives XY coordinates // Set coordinates for volume bar polygons setXY4 ( current[LEFTBAR], MINBAR, BARTOP, lMax + TSIZE, BARTOP, MINBAR, BARBOTTOM, lMax + TSIZE, BARBOTTOM); setXY4 (current[RIGHTBAR], MINBAR, BARTOP + MARGIN, rMax + TSIZE, BARTOP + MARGIN, MINBAR, BARBOTTOM + MARGIN, rMax + TSIZE, BARBOTTOM + MARGIN); // Set coordinates for peak cursor polygons setXY4 (peak[LEFTBAR], lPeak, BARTOP, lPeak + TSIZE, BARTOP, lPeak, BARBOTTOM, lPeak + TSIZE, BARBOTTOM); setXY4 (peak[RIGHTBAR], rPeak, BARTOP + MARGIN, rPeak + TSIZE, BARTOP + MARGIN, rPeak, BARBOTTOM + MARGIN, rPeak + TSIZE, BARBOTTOM + MARGIN); // Add prims to ordering table from bottom to top for ( int i = 0; i < BARNUM; i++) { addPrim(ot[db][OTLEN - 1], bar[i]); addPrim(ot[db][OTLEN - 2], current[i]); addPrim(ot[db][OTLEN - 3], peak[i]); } // Get current track number ~ every second // See LibeOver47.pdf, p.188 if (counter%50 == 0){ CdReady(1, &result[0]); // current track number can also be obtained with // CdControlB (CdlGetlocP, 0, &result[0]); } // Switch track after ~ 20 seconds if (counter%(50*20) == 0){ // Flip can have a value of 1 or -1 flip *= -1; uint8_t nextTrackIndex = result[1] + flip; // Send CD command to switch track CdControlB (CdlPlay, (u_char *)&loc[ nextTrackIndex ], 0); } // Update current and peak values findSampleMaxVolume(); // Print bar's infos printDataInfo(); // Draw debug stream FntPrint(2, "Hello SPU readback ! %d\n", counter); FntPrint(2, "Current track: %d\n", result[1] ); FntPrint(2, "L: %08d, R: %08d\n", leftMax, rightMax); FntPrint(2, "SPU Addr: 0x%03x ", SpuIrqAddr ); FntFlush(2); // Update display display(); } return 0; } ================================================ FILE: hello_spu_readback/isoconfig.xml ================================================ ================================================ FILE: hello_spu_readback/system.cnf ================================================ BOOT=cdrom:\SCES_313.37;1 TCB=4 EVENT=10 STACK=801FFFF0 ================================================ FILE: hello_str/Makefile ================================================ .PHONY: all cleansub all: mkpsxiso -y ./isoconfig.xml cleansub: $(MAKE) clean rm -f hello_str.cue hello_str.bin TARGET = hello_str SRCS = hello_str.c \ include ../common.mk ================================================ FILE: hello_str/README.md ================================================ This example will play a fullscreen STR file and is as straightforward as possible. If you need more advanced control other the display size and position, see the [STR playback library](https://github.com/ABelliqueux/nolibgs_hello_worlds/tree/main/hello_str#str-playback-library) section. ## Compiling You need [mkpsxiso](https://github.com/Lameguy64/mkpsxiso) in your $PATH to generate a PSX disk image. Typing ```bash make ``` in a terminal will compile and generate the bin/cue files. Typing ```bash make cleansub ``` will clean the current directory. ## STR playback library @Lameguy64 has spent some time making a STR playback library that's easily included in a project : > One thing that I find somewhat missing here is a decent piece of code for playing STR video files easily. So, what I did was take the old and messy PsyQ STR player example, clean it up entirely, and finally make it into a cute little c library for easy implementation. Original post : http://www.psxdev.net/forum/viewtopic.php?t=507 Original download link : https://www.mediafire.com/download/s61u86sxd1djncy/strplay.7z Mirror : http://psx.arthus.net/code/strplay.7z ## Converting to AVI You need `AVI file 320x240, 15 fps, 24-bit color, Stereo 16-bit sound @ 44100 Hz`. ``` Stream #0:0: Video: rawvideo, bgr24, 320x240, 27763 kb/s, 15 fps, 15 tbr, 15 tbn, 15 tbc Stream #0:1: Audio: pcm_u8 ([1][0][0][0] / 0x0001), 44100 Hz, 2 channels, u8, 705 kb/s ``` ### Video to AVI Use Virtualdub or ffmpeg : ```bash ffmpeg -i $INPUT.MKV -vcodec rawvideo -pix_fmt bgr24 -vf scale=320:240,setsar=1:1 -acodec pcm_u8 -ar 44100 -r 15 $OUTPUT.avi ``` ### AVI to STR Use [`MC32.EXE`](http://psx.arthus.net/tools/pimp-psx.zip) to convert the AVI file to STR using these settings : ``` Format : Input : Avi (Uncompressed), Output : str (MDEC) Sound: 37.8 KHz, Stereo; Easy: Double Speed, 15 fps, 1ch, Leap Sector; MDEC: version 2 ``` ![MC32-avi-str](https://wiki.arthus.net/assets/MC32-avi-str.png) **If `MC32.exe` crashes when hitting the 'Go' button, you have to open the ffmpeg AVI file in virtualdub, then save it again ; `File > Save as AVI...` or `F7` key, then retry.** You should now have a STR file and a XA file that you have to interleave in `MC32`: ``` Format : Input : str (MDEC), Output: str (MDEC) Sound: Input: XA , 37.8 KHz, Stereo; Frame rate: 15 fps, # Channels : 1(150sectors/s), Leap Sector; CD-ROM speed : Double Speed; ``` ![MC32-avi-str-interleave](https://wiki.arthus.net/assets/MC32-avi-str-interleaved.png) ### Finding a video's frame count With `ffmpeg` : ```bash ffprobe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 VIDEOFILE.AVI ``` Alternatively, open the STR file in `MC32.exe` and look at the bottom left of the window. ### Tools & Refs MC32 : http://psx.arthus.net/tools/pimp-psx.zip STR converter : http://psx.arthus.net/tools/str_converter.rar Original PsyQ sample code : ``` /psyq/psx/sample/scee/CD/MOVIE2 /psyq/addons/cd/MOVIE (same as /psx/sample/cd/MOVIE ) /addons/sound/STREAM/TUTO2.C ``` Original post : http://www.psxdev.net/forum/viewtopic.php?t=507 Video to STR conversion tutorial : http://www.psxdev.net/forum/viewtopic.php?f=51&t=277 MDEC notes : http://psx.arthus.net/sdk/Psy-Q/DOCS/TECHNOTE/mdecnote.pdf ## Video credits The video and song used in this example are by Nina Paley : https://archive.org/details/CopyingIsNotTheft-ScratchTrack1280X720Hdv ================================================ FILE: hello_str/hello_str.c ================================================ // Stream a STR file from CD, decompress and play it. // Schnappy 07-2021 // based on Lameguy64 strplay library : http://www.psxdev.net/forum/viewtopic.php?t=507 // Original PsyQ sample code : /psyq/addons/cd/MOVIE // Video to STR conversion : https://github.com/ABelliqueux/nolibgs_hello_worlds/tree/main/hello_str #include #include #include #include #include #include // CD library #include // CODEC library #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define TRUECOL 0 // 0 : 16bpp, 1: 24bpp #define SCREENXRES 320 // Screen width #define SCREENYRES 240 + (VMODE << 4) // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256 #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 8 // margins for text display #define MARGINY 16 #define FONTSIZE 8 * 7 // Text Field Height DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; short db = 0; // index of which buffer is used, values 0, 1 #define STR_POS_X 0 // If PAL mode, add 8 pixels offset on Y (256-240)/2 #define STR_POS_Y (VMODE << 4)/2 // Ring Buffer size (32 sectors seems good enough) #define RING_SIZE 32 #if TRUECOL // pixels per short word (16b/2B) // 1px is 3B in 24bpp // 1px is 2B(one word) in 16bpp // therefore 2B will hold 3/2 pixels #define PPW 3/2 // DCT mode - bit 0 : depth (0 = 16b, 1 = 24b), bit 1: in 16b mode, set STP(Semi-Transparency) bit 15. // 24bpp = 01b => 1 #define DCT_MODE 1 #else #define PPW 1 // 16bpp = 10b => 2 #define DCT_MODE 2 #endif // Stop playback if set static int endPlayback = 0; // STR file infos : Filename on CD, Width, Height, Length in frames static char * StrFileName = "\\COPY.STR;1"; static int StrFileX = 320; static int StrFileY = 240; static int StrFileLength = 893; // When using RGB24, a special routine has to be setup as a callback function to avoid MDEC/CDROM conflict // See http://psx.arthus.net/sdk/Psy-Q/DOCS/LibRef47.pdf , p.713 static void strCheckRGB24(); void init(void) { ResetCallback(); ResetGraph(0); // Initialize drawing engine with a complete reset (0) SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1] SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1] SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1] SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1] // Set video mode #if VMODE SetVideoMode(MODE_PAL); disp[0].disp.y = 8; disp[1].disp.y = 8; #endif SetDispMask(1); // Display on screen setRGB0(&draw[0], 155, 0, 150); // set color for first draw area setRGB0(&draw[1], 155, 0, 150); // set color for second draw area draw[0].isbg = 0; // set mask for draw areas. 1 means repainting the area with the RGB color each frame draw[1].isbg = 0; #if TRUECOL disp[0].isrgb24 = 1; disp[1].isrgb24 = 1; #endif PutDispEnv(&disp[db]); // set the disp and draw environnments PutDrawEnv(&draw[db]); FntLoad(960, 0); // Load font to vram at 960,0(+128) FntOpen(MARGINX, MARGINY, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars } void display(void) { DrawSync(0); // Wait for all drawing to terminate VSync(0); // Wait for the next vertical blank PutDispEnv(&disp[db]); // set alternate disp and draw environnments PutDrawEnv(&draw[db]); db = !db; // flip db value (0 or 1) } int main() { // CD File descriptor CdlFILE STRfile; // Parameter we want to set the CDROM with u_char param=CdlModeSpeed; // Buffers total ~110KB in memory // SECTOR_SIZE is defined in words : 512 * 4 == 2048 Bytes/sector // Ring buffer : 32 * 2048 = 65536 Bytes u_long RingBuff[ RING_SIZE * SECTOR_SIZE ]; // VLC buffers : display area in words (hence /2), 160*320 == 38400 Bytes u_long VlcBuff[2][ SCREENXRES / 2 * StrFileY ]; // If using 16bpp, fetch 16xYres strips, if 24bpp, fetch 24xYres strips, 5120*PPW Bytes u_short ImgBuff[2][ 16 * PPW * StrFileY]; u_long * curVLCptr = VlcBuff[db]; // Init Disp/Draw env, Font, etc. init(); SpuInit(); // Init CDrom system CdInit(); // Reset the MDEC DecDCTReset(0); // Set callback routine DecDCToutCallback(strCheckRGB24); // Set ring buffer StSetRing(RingBuff, RING_SIZE); // Set streaming parameters StSetStream(TRUECOL, 1, StrFileLength, 0, 0); // Get the CD location of the STR file to play if ( CdSearchFile(&STRfile, StrFileName) == 0 ) { FntPrint("File not found :%s\n", StrFileName); } // Set the seek target position CdControl(CdlSetloc, (u_char *)&STRfile.pos, 0); // Set CD mode to CdlModeSpeed CdControl(CdlSetmode, ¶m, 0); // Read from CD at position &STRfile.pos // Enable streaming, double speed and ADPCM playback CdRead2(CdlModeStream|CdlModeSpeed|CdlModeRT); // Use a counter to avoid deadlocks int wait = WAIT_TIME; // Next Ring Buffer Frame address u_long * nextFrame = 0; // Ring buffer frame address u_long * frameAddr = 0; // Ring buffer frame header StHEADER * sectorHeader; // Main loop while (1) { // Set some pointers to the relevant buffer addresses u_long * curVLCptr = &VlcBuff[!db][0]; u_short * curIMGptr = &ImgBuff[db][0]; // While end of str is not reached, play it while (!endPlayback) { // Use this area to draw the slices RECT curSlice = { STR_POS_X, (db * StrFileY) + STR_POS_Y, // In 24bpp, use 24 pixels wide slices 16 * PPW , StrFileY}; int frameDone = 0; // Reset counter wait = WAIT_TIME; // Dont try decoding if not data has been loaded from ring buffer if ( frameAddr ){ // Begin decoding RLE-encoded MDEC image data DecDCTin( curVLCptr , DCT_MODE); // Prepare to receive the decoded image data from the MDEC while (curSlice.x < STR_POS_X + StrFileX * PPW) { // Receive decoded data : a 16*ppw*240 px slice in long word (4B), so / 2 DecDCTout( (u_long *) curIMGptr, curSlice.w * curSlice.h / 2); // Wait for transfer end DecDCToutSync(0); // Transfer data from main memory to VRAM LoadImage(&curSlice, (u_long *) curIMGptr ); // Wait for drawing termination DrawSync(0); // Increment drawArea's X with slice width (16 or 24 pix) curSlice.x += 16 * PPW; } // Set frameDone flag to 1 frameDone = 1; curSlice.x = STR_POS_X; curSlice.y = (db * StrFileY) + STR_POS_Y; } // Get one frame of ring buffer data // StGetNext is non-blocking, so we wait for it to return 0. // StGetNext will lock the region at &frameAddr until StFreeRing() is called. while ( StGetNext((u_long **)&frameAddr,(u_long **)§orHeader) ) { wait--; if (wait == 0) break; } // If the current frame's number is bigger than the number of frames in STR, // set the endPlayback flag. if (sectorHeader->frameCount >= StrFileLength) endPlayback = 1; // Grab a frame from the stream wait = WAIT_TIME; while ((nextFrame = frameAddr) == 0) { wait--; if ( wait == 0 ){ break; } } // Decode the Huffman/VLC compressed data DecDCTvlc(nextFrame, &VlcBuff[!db][0]); // Unlock area obtained by StGetNext() StFreeRing(nextFrame); // Reset counter wait = WAIT_TIME; // Wait until the whole frame is loaded to VRAM while ( frameDone == 0 ) { wait--; if ( wait == 0 ) { // If a timeout occurs, force switching buffers frameDone = 1; curSlice.x = STR_POS_X; curSlice.y = (db * StrFileY) + STR_POS_Y; } } FntFlush(-1); display(); } // Disable callback DecDCToutCallback(0); // Release two interrupt functions CdDataCallback() and CdReadyCallback() hooked by CDRead2() StUnSetRing(); // Put CDROM on pause at current position CdControlB(CdlPause, 0, 0); } return 0; }; static void strCheckRGB24() { /* From http://psx.arthus.net/sdk/Psy-Q/DOCS/, p.713 * When playing a movie in 24-bit mode, there is a potential hardware conflict between the CD subsystem * and the MDEC image decompression system which can result in corrupted data. To avoid this, * StCdInterrupt() may defer transferring a sector and instead set a flag variable called StCdInterFlag to * indicate that a CD sector is ready to be transferred. Once the MDEC is finished transferring data, your * application should check StCdIntrFlag and call StCdInterrupt() directly if it is set. */ #if TRUECOL extern int StCdIntrFlag; // If flag was set if ( StCdIntrFlag ) { // Trigger data transfer StCdInterrupt(); // Reset flag StCdIntrFlag = 0; } #endif } ================================================ FILE: hello_str/isoconfig.xml ================================================ ================================================ FILE: hello_str/str/copyings.str ================================================ [File too large to display: 19.9 MB] ================================================ FILE: hello_str/system.cnf ================================================ BOOT=cdrom:\SCES_313.37;1 TCB=4 EVENT=10 STACK=801FFFF0 ================================================ FILE: hello_strplay/Makefile ================================================ .PHONY: all cleansub all: mkpsxiso -y ./isoconfig.xml cleansub: $(MAKE) clean rm -f hello_strplay.cue hello_strplay.bin TARGET = hello_strplay SRCS = hello_strplay.c \ include ../common.mk ================================================ FILE: hello_strplay/README.md ================================================ This example show how to use Lameguy64's [STR playback library](https://github.com/ABelliqueux/nolibgs_hello_worlds/tree/main/hello_str#str-playback-library). For a barebone example, see the [hello_str](https://github.com/ABelliqueux/nolibgs_hello_worlds/tree/main/hello_str) example. ## Compiling You need [mkpsxiso](https://github.com/Lameguy64/mkpsxiso) in your $PATH to generate a PSX disk image. Typing ```bash make ``` in a terminal will compile and generate the bin/cue files. Typing ```bash make cleansub ``` will clean the current directory. ## STR playback library @Lameguy64 has spent some time making a STR playback library that's easily included in a project : > One thing that I find somewhat missing here is a decent piece of code for playing STR video files easily. So, what I did was take the old and messy PsyQ STR player example, clean it up entirely, and finally make it into a cute little c library for easy implementation. Original post : http://www.psxdev.net/forum/viewtopic.php?t=507 Original download link : https://www.mediafire.com/download/s61u86sxd1djncy/strplay.7z Mirror : http://psx.arthus.net/code/strplay.7z ## Video encoding and more informations See the [wiki](https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/STR). ## Video credits The video and song used in this example are by Nina Paley : https://archive.org/details/CopyingIsNotTheft-ScratchTrack1280X720Hdv ================================================ FILE: hello_strplay/hello_strplay.c ================================================ // Using the strplay library. // Schnappy 07-2021 // based on Lameguy64 strplay library : http://www.psxdev.net/forum/viewtopic.php?t=507 // Original PsyQ sample code : /psyq/addons/cd/MOVIE // Video to STR conversion : https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/STR #include #include #include #include #include // CD library #include // CODEC library #include // include Lameguy64's library #include "strplay.c" #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define TRUECOL 1 // 0 : 16bpp, 1: 24bpp #define SCREENXRES 320 // Screen width #define SCREENYRES 240 + (VMODE << 4) // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256 #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 8 // margins for text display #define MARGINY 16 #define FONTSIZE 8 * 7 // Text Field Height DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; short db = 0; // index of which buffer is used, values 0, 1 STRFILE StrFile[] = { // File name Resolution Frame count "\\COPY.STR;1", 320, 240, 893 }; // When using RGB24, a special routine has to be setup as a callback function to avoid MDEC/CDROM conflict // See http://psx.arthus.net/sdk/Psy-Q/DOCS/LibRef47.pdf , p.713 static void strCheckRGB24(); void init(void) { ResetCallback(); ResetGraph(0); // Initialize drawing engine with a complete reset (0) SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1] SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1] SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1] SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1] // Set video mode #if VMODE SetVideoMode(MODE_PAL); disp[0].disp.y = 8; disp[1].disp.y = 8; #endif SetDispMask(1); // Display on screen setRGB0(&draw[0], 155, 0, 150); // set color for first draw area setRGB0(&draw[1], 155, 0, 150); // set color for second draw area draw[0].isbg = 0; // set mask for draw areas. 1 means repainting the area with the RGB color each frame draw[1].isbg = 0; #if TRUECOL disp[0].isrgb24 = 1; disp[1].isrgb24 = 1; #endif PutDispEnv(&disp[db]); // set the disp and draw environnments PutDrawEnv(&draw[db]); FntLoad(960, 0); // Load font to vram at 960,0(+128) FntOpen(MARGINX, MARGINY, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars } void display(void) { DrawSync(0); // Wait for all drawing to terminate VSync(0); // Wait for the next vertical blank PutDispEnv(&disp[db]); // set alternate disp and draw environnments PutDrawEnv(&draw[db]); db = !db; // flip db value (0 or 1) } void main() { // Reset and initialize stuff init(); CdInit(); PadInit(0); // Play the video in loop while (1) { if (PlayStr(320, 240, 0, 0, &StrFile[0]) == 0) // If player presses Start break; // Exit the loop } } static void strCheckRGB24() { /* From http://psx.arthus.net/sdk/Psy-Q/DOCS/, p.713 * When playing a movie in 24-bit mode, there is a potential hardware conflict between the CD subsystem * and the MDEC image decompression system which can result in corrupted data. To avoid this, * StCdInterrupt() may defer transferring a sector and instead set a flag variable called StCdInterFlag to * indicate that a CD sector is ready to be transferred. Once the MDEC is finished transferring data, your * application should check StCdIntrFlag and call StCdInterrupt() directly if it is set. */ #if TRUECOL extern u_long StCdIntrFlag; // If flag was set if ( StCdIntrFlag ) { // Trigger data transfer StCdInterrupt(); // Reset flag StCdIntrFlag = 0; } #endif } ================================================ FILE: hello_strplay/isoconfig.xml ================================================ ================================================ FILE: hello_strplay/str/copyings.str ================================================ [File too large to display: 19.9 MB] ================================================ FILE: hello_strplay/strplay.c ================================================ /* Simple STR Player Library by Lameguy64 (?) 2014 Meido-Tek Productions/Lame Studios Original PsyQ sample programmed by: Yutaka Suzu Masa Ume Code heavily refined by: Lameguy64 What Lameguy did to the original code: - Removed all of the icky yucky UTF-16 junk - Fixed all crap-English comments - Greatly improved code formatting - Renamed variables with better names - Buffer arrays are now initialized only when the playback routine is called Libraries Required: libetc libgte libgpu libcd Function list: int PlayStr(int xres, int yres, int xpos, int ypos, STRFILE *str) Parameters: xres, yres - Video resolution. xpos, ypos - Framebuffer offset on where to draw the video. STRFILE *str - STRFILE entry to play. Notes: Just make sure that you have at least 192KB of free memory before calling the PlayStr function otherwise, the console will crash. As for the video resolution, it must be equal or less than 256 as the second buffer is located directly below the first buffer. Note: If compiling the sample fails, open your psyq.ini file located in \psyq\bin and append the following into the stdlib line: libds.lib libpress.lib */ #define IS_RGB24 1 // 0:16-bit playback, 1:24-bit playback (recommended for quality) #define RING_SIZE 32 // Ring Buffer size (32 sectors seems good enough) #if IS_RGB24==1 #define PPW 3/2 // pixels per short word #define DCT_MODE 3 // Decode mode for DecDCTin routine #else #define PPW 1 #define DCT_MODE 2 #endif // A simple struct to make STR handling a bit easier typedef struct { char FileName[32]; int Xres; int Yres; int NumFrames; } STRFILE; // Decode environment typedef struct { u_long *VlcBuff_ptr[2]; // Pointers to the VLC buffers u_short *ImgBuff_ptr[2]; // Pointers to the frame slice buffers RECT rect[2]; // VRAM parameters on where to draw the frame data to RECT slice; // Frame slice parameters for loading into VRAM int VlcID; // Current VLC buffer ID int ImgID; // Current slice buffer ID int RectID; // Current video buffer ID int FrameDone; // Frame decode completion flag } STRENV; // A bunch of internal variables static STRENV strEnv; static int strScreenWidth=0,strScreenHeight=0; static int strFrameX=0,strFrameY=0; static int strNumFrames=0; static int strFrameWidth=0,strFrameHeight=0; // Frame size of STR file static int strPlayDone=0; // Playback completion flag // Main function prototypes int PlayStr(int xres, int yres, int xpos, int ypos, STRFILE *str); // Internal function prototypes static void strDoPlayback(STRFILE *str); static void strCallback(); static void strNextVlc(STRENV *strEnv); static void strSync(STRENV *strEnv, int mode); static u_long *strNext(STRENV *strEnv); static void strKickCD(CdlLOC *loc); int PlayStr(int xres, int yres, int xpos, int ypos, STRFILE *str) { /* Main STR playback routine. Returns: 0 - Playback failed or was skipped. 1 - Playback was finished. */ strNumFrames=str->NumFrames; strScreenWidth=xres; strScreenHeight=yres; strFrameX=xpos; strFrameY=ypos; strPlayDone=0; strDoPlayback(str); if (strPlayDone == 0) return(0); else return(1); } static void strDoPlayback(STRFILE *str) { /* Does the actual STR playback. */ int id; // Display buffer ID DISPENV disp; // Display environment CdlFILE file; // File info of video file // Buffers initialized here so we won't waste too much memory for playing FMVs // (just make sure you have at least 192KB of free memory before calling this routine) u_long RingBuff[RING_SIZE*SECTOR_SIZE]; // Ring buffer u_long VlcBuff[2][str->Xres/2*str->Yres]; // VLC buffers u_short ImgBuff[2][16*PPW*str->Yres]; // Frame 'slice' buffers // Set display mask so we won't see garbage while the stream is being prepared SetDispMask(0); // Get the CD location of the STR file to play if (CdSearchFile(&file, str->FileName) == 0) { #ifdef DEBUG printf("ERROR: I cannot find video file %s\n", str->FileName); #endif SetDispMask(1); return; } // Setup the buffer pointers strEnv.VlcBuff_ptr[0] = &VlcBuff[0][0]; strEnv.VlcBuff_ptr[1] = &VlcBuff[1][0]; strEnv.VlcID = 0; strEnv.ImgBuff_ptr[0] = &ImgBuff[0][0]; strEnv.ImgBuff_ptr[1] = &ImgBuff[1][0]; strEnv.ImgID = 0; // Setup the display buffers on VRAM strEnv.rect[0].x = strFrameX; // First page strEnv.rect[0].y = strFrameY; strEnv.rect[1].x = strFrameX; // Second page strEnv.rect[1].y = strFrameY+strScreenHeight; strEnv.RectID = 0; // Set the parameters for uploading frame slices strEnv.slice.x = strFrameX; strEnv.slice.y = strFrameY; strEnv.slice.w = 16*PPW; strEnv.FrameDone = 0; // Reset the MDEC DecDCTReset(0); // Set callback routine DecDCToutCallback(strCallback); // Set ring buffer StSetRing(RingBuff, RING_SIZE); // Set streaming parameters StSetStream(IS_RGB24, 1, 0xffffffff, 0, 0); // Begin streaming! strKickCD(&file.pos); // Load the first frame of video before entering main loop strNextVlc(&strEnv); while (1) { // Decode the compressed frame data DecDCTin(strEnv.VlcBuff_ptr[strEnv.VlcID], DCT_MODE); // Prepare to receive the decoded image data from the MDEC DecDCTout((u_long*)strEnv.ImgBuff_ptr[strEnv.ImgID], strEnv.slice.w*strEnv.slice.h/2); // Get the next frame strNextVlc(&strEnv); // Wait for the frame to finish decoding strSync(&strEnv, 0); // Switch between the display buffers per frame id = strEnv.RectID? 0: 1; SetDefDispEnv(&disp, 0, strScreenHeight*id, strScreenWidth*PPW, strScreenHeight); // Set parameters for 24-bit color mode #if IS_RGB24 == 1 disp.isrgb24 = IS_RGB24; disp.disp.w = disp.disp.w*2/3; #endif VSync(0); // VSync to avoid screen tearing PutDispEnv(&disp); // Apply the video parameters SetDispMask(1); // Remove the display mask if(strPlayDone == 1) { break; } if(PadRead(1) & PADstart) { // stop button pressed exit animation routine break; } } // Shutdown streaming DecDCToutCallback(0); StUnSetRing(); CdControlB(CdlPause, 0, 0); } static void strCallback() { /* Callback routine which is called whenever a slice has finished decoding. All it does is transfer the decoded slice into VRAM. */ RECT TransferRect; int id; // In 24-bit color, StCdInterrupt must be called in every callback #if IS_RGB24==1 extern u_long StCdIntrFlag; if (StCdIntrFlag) { StCdInterrupt(); StCdIntrFlag = 0; } #endif id = strEnv.ImgID; TransferRect = strEnv.slice; // Switch slice buffers strEnv.ImgID = strEnv.ImgID? 0:1; // Step to next slice strEnv.slice.x += strEnv.slice.w; // Frame not yet decoded completely? if (strEnv.slice.x < strEnv.rect[strEnv.RectID].x + strEnv.rect[strEnv.RectID].w) { // Prepare for next slice DecDCTout((u_long*)strEnv.ImgBuff_ptr[strEnv.ImgID], strEnv.slice.w*strEnv.slice.h/2); } else { // Frame has been decoded completely // Set the FrameDone flag strEnv.FrameDone = 1; // Switch display buffers strEnv.RectID = strEnv.RectID? 0: 1; strEnv.slice.x = strEnv.rect[strEnv.RectID].x; strEnv.slice.y = strEnv.rect[strEnv.RectID].y; } // Transfer the slice into VRAM LoadImage(&TransferRect, (u_long *)strEnv.ImgBuff_ptr[id]); } static void strNextVlc(STRENV *strEnv) { /* Performs VLC decoding and grabs a frame from the stream. */ int cnt=WAIT_TIME; u_long *next; u_long *strNext(); // Grab a frame from the stream while ((next = strNext(strEnv)) == 0) { if (--cnt == 0) // Timeout handler return; } // Switch VLC buffers strEnv->VlcID = strEnv->VlcID? 0: 1; // Decode the VLC DecDCTvlc(next, strEnv->VlcBuff_ptr[strEnv->VlcID]); // Free the ring buffer StFreeRing(next); } static u_long *strNext(STRENV *strEnv) { /* Grabs a frame of video from the stream. */ u_long *addr; StHEADER *sector; int cnt = WAIT_TIME; // Grab a frame while (StGetNext((u_long **)&addr,(u_long **)§or)) { if (--cnt == 0) // Timeout handler return(0); } // If the frame's number has reached number of frames the video has, // set the strPlayDone flag. if (sector->frameCount >= strNumFrames) strPlayDone = 1; // if the resolution is differ to previous frame, clear frame buffer if (strFrameWidth != sector->width || strFrameHeight != sector->height) { RECT rect; setRECT(&rect, 0, 0, strScreenWidth * PPW, strScreenHeight*2); ClearImage(&rect, 0, 0, 0); strFrameWidth = sector->width; strFrameHeight = sector->height; } // set STRENV according to the data on the STR format strEnv->rect[0].w = strEnv->rect[1].w = strFrameWidth*PPW; strEnv->rect[0].h = strEnv->rect[1].h = strFrameHeight; strEnv->slice.h = strFrameHeight; return(addr); } static void strSync(STRENV *strEnv, int mode) { /* Waits for the frame to finish decoding. */ u_long cnt = WAIT_TIME; // Wait for the frame to finish decoding while (strEnv->FrameDone == 0) { if (--cnt == 0) { // Timeout handler // If a timeout occurs, force switching buffers #ifdef DEBUG printf("ERROR: A frame cannot be played!\n"); #endif strEnv->FrameDone = 1; strEnv->RectID = strEnv->RectID? 0: 1; strEnv->slice.x = strEnv->rect[strEnv->RectID].x; strEnv->slice.y = strEnv->rect[strEnv->RectID].y; } } strEnv->FrameDone = 0; } static void strKickCD(CdlLOC *loc) { /* Begins CD streaming. */ u_char param=CdlModeSpeed; loop: // Seek to the STR file to play while (CdControl(CdlSetloc, (u_char *)loc, 0) == 0); while (CdControl(CdlSetmode, ¶m, 0) == 0); VSync(3); // Wait for 3 screen cycles before changing drive speed // Start streaming if(CdRead2(CdlModeStream|CdlModeSpeed|CdlModeRT) == 0) goto loop; // If it fails, try again } ================================================ FILE: hello_strplay/system.cnf ================================================ BOOT=cdrom:\SCES_313.37;1 TCB=4 EVENT=10 STACK=801FFFF0 ================================================ FILE: hello_tile/Makefile ================================================ TARGET = hello_tile SRCS = hello_tile.c \ include ../common.mk ================================================ FILE: hello_tile/hello_tile.c ================================================ // Draw a tile primitive // Schnappy 2021 // based on Lameguy64's tutorial : http://lameguy64.net/svn/pstutorials/chapter1/2-graphics.html #include #include #include #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 #define SCREENYRES 240 #define CENTERX SCREENXRES/2 #define CENTERY SCREENYRES/2 #define MARGINX 0 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 7 // Text Field Height #define OTLEN 8 // Ordering Table Length DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; u_long ot[2][OTLEN]; // double ordering table of length 8 * 32 = 256 bits / 32 bytes char primbuff[2][32768];// double primitive buffer of length 32768 * 8 = 262.144 bits / 32,768 Kbytes char *nextpri = primbuff[0]; // pointer to the next primitive in primbuff. Initially, points to the first bit of primbuff[0] short db = 0; // index of which buffer is used, values 0, 1 void init(void) { ResetGraph(0); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], 50, 50, 50); setRGB0(&draw[1], 50, 50, 50); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); // We're using a reverse OT, so we want to display the last item first. See PsyQ's LibRef47.pdf, p.277 DrawOTag(&ot[db][OTLEN - 1]); // Comment above line, and uncomment the following line to use a regular oredered OT. Comment l.71 and Uncomment l.73 accordingly //~ DrawOTag(ot[db]); db = !db; nextpri = primbuff[db]; } int main(void) { // These two tiles are added at the same OT index TILE * blue_tile; TILE * pink_tile; // This one is added at a different OT index TILE * yellow_tile; init(); while (1) { // Initialize the reversed ordering table. This means the elements at index OTLEN - 1 is drawn first. ClearOTagR(ot[db], OTLEN); // Use regular order OT, uncomment l.53 accordingly //~ ClearOTag(ot[db], OTLEN); // yellow_tile is before pink and blue tile in the code, // and it displays behind because it is added to a different ot index (od[db] + OTLEN - 1) // Using a Regular or Reverse OT will have an effect on drawing order. (See lines 53 and 73) yellow_tile = (TILE * ) nextpri; // yellow_tile is a pointer to primbuf content at adress nextpri, that's cast (type converted) to a TILE struc. setTile(yellow_tile); // initialize the TILE structure ( fill the length and tag(?) value ) setXY0(yellow_tile, CENTERX - 32 , CENTERY - 48); // Set X,Y setWH(yellow_tile, 128, 40); // Set Width, Height setRGB0(yellow_tile, 255, 255, 0); // Set color addPrim(ot[db][OTLEN - 1], yellow_tile); // Add primitive to ordering table nextpri += sizeof(TILE); // blue_tile added at od[db] + OTLEN - 2 blue_tile = (TILE * ) nextpri; // blue_tile is a pointer to primbuf content at adress nextpri, that's cast (type converted) to a blue_tile struc. setTile(blue_tile); // initialize the blue_tile structure ( fill the length and tag(?) value ) setXY0(blue_tile, CENTERX - 16, CENTERY - 32); // Set X,Y setWH(blue_tile, 32, 64); // Set Width, Height setRGB0(blue_tile, 60, 180, 255); // Set color addPrim(ot[db][OTLEN - 2], blue_tile); // Add primitive to ordering table nextpri += sizeof(TILE); // Increment the adress nextpri points to by the size of TILE struct // pink_tile is after blue_tile in the code, // so it is drawn before, thus under blue_tile. // However, it is added at the same ot index (od[db] + OTLEN - 2) // so using a Regular or Reverse OT won't have an effect on drawing order. pink_tile = (TILE * ) nextpri; // pink_tile is a pointer to primbuf content at adress nextpri, that's cast (type converted) to a TILE struc. setTile(pink_tile); // initialize the TILE structure ( fill the length and tag(?) value ) setXY0(pink_tile, CENTERX, CENTERY - 64); // Set X,Y setWH(pink_tile, 64, 64); // Set Width, Height setRGB0(pink_tile, 255, 32, 255); // Set color addPrim(ot[db][OTLEN - 2], pink_tile); // Add primitive to ordering table nextpri += sizeof(TILE); FntPrint("Hello tile !"); FntFlush(-1); display(); } return 0; } ================================================ FILE: hello_vag/Makefile ================================================ TARGET = hello_vag SRCS = hello_vag.c \ ../VAG/hello_poly.vag include ../common.mk ================================================ FILE: hello_vag/README.md ================================================ See here for more informations about the VAG fileformat and tools : https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/VAG ================================================ FILE: hello_vag/hello_vag.c ================================================ // VAGDEMO2020 by Schnappy // December 2020 // Based on VAGDEMO_FIXED by Yagotzirck // Based on VAGDEMO by Shadow // based on psyq/addons/sound/TUTO3.C // // // Load a VAG file to SPU sound buffer and play it back. // // WAV creation: use ffmpeg to create a 16-bit ADPCM mono WAV file - change -ar to reduce filesize (and quality) // $ ffmpeg -i input.mp3 -acodec pcm_s16le -ac 1 -ar 44100 output.wav // // WAV to VAG convertion using WAV2VAG : https://github.com/ColdSauce/psxsdk/blob/master/tools/wav2vag.c // change -freq according to the -ar setting above // $ wav2vag input.wav output.vag -sraw16 -freq=44100 (-L) // // Alternatively, you can use PsyQ VAGEDIT.EXE to change the sampling frequency of an existing VAG file. // // Docs : see libformat47.pdf p.209 // libover47.pdf, p.271 // libref47.pdf, p.980 // URLS : http://psx.arthus.net/code/VAG/ #include #include #include #include #include // Sound system #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 #define SCREENYRES 240 #define CENTERX SCREENXRES/2 #define CENTERY SCREENYRES/2 #define MARGINX 0 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 7 // Text Field Height DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; short db = 0; // index of which buffer is used, values 0, 1 // Sound stuff #define MALLOC_MAX 3 // Max number of time we can call SpuMalloc //~ // convert Little endian to Big endian #define SWAP_ENDIAN32(x) (((x)>>24) | (((x)>>8) & 0xFF00) | (((x)<<8) & 0x00FF0000) | ((x)<<24)) typedef struct VAGheader{ // All the values in this header must be big endian char id[4]; // VAGp 4 bytes -> 1 char * 4 unsigned int version; // 4 bytes unsigned int reserved; // 4 bytes unsigned int dataSize; // (in bytes) 4 bytes unsigned int samplingFrequency;// 4 bytes char reserved2[12]; // 12 bytes -> 1 char * 12 char name[16]; // 16 bytes -> 1 char * 16 // Waveform data after that }VAGhdr; SpuCommonAttr commonAttributes; // structure for changing common voice attributes SpuVoiceAttr voiceAttributes ; // structure for changing individual voice attributes u_long vag_spu_address; // address allocated in memory for first sound file // DEBUG : these allow printing values for debugging u_long spu_start_address; u_long get_start_addr; u_long transSize; // Memory management table ; allow MALLOC_MAX calls to SpuMalloc() - libref47.pdf p.1044 char spu_malloc_rec[SPU_MALLOC_RECSIZ * (MALLOC_MAX+1)]; // VAG files // We're using GrumpyCoder's Nugget wrapper to compile the code with a modern GCC : https://github.com/grumpycoders/pcsx-redux/tree/main/src/mips/psyq // To include binary files in the exe, add your VAG files to the SRCS variable in Makefile // and in common.mk, add this rule to include *.vag files : // //~ %.o: %.vag //~ $(PREFIX)-objcopy -I binary --set-section-alignment .data=4 --rename-section .data=.rodata,alloc,load,readonly,data,contents -O elf32-tradlittlemips -B mips $< $@ // hello_poly.vag - 44100 Khz extern unsigned char _binary____VAG_hello_poly_vag_start[]; // filename must begin with _binary_ followed by the full path, with . and / replaced, and then suffixed with _ and end with _start[]; or end[]; extern unsigned char _binary____VAG_hello_poly_vag_end[]; // Going up one directory level is 4 '_' : ____ as ./ is replaced by __ // https://discord.com/channels/642647820683444236/663664210525290507/780866265077383189 void initGraph(void) { ResetGraph(0); SetDefDispEnv(&disp[0], 0, 0, SCREENXRES, SCREENYRES); SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); SetDefDrawEnv(&draw[1], 0, 0, SCREENXRES, SCREENYRES); if (VMODE) { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], 50, 50, 50); setRGB0(&draw[1], 50, 50, 50); draw[0].isbg = 1; draw[1].isbg = 1; PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); FntLoad(960, 0); FntOpen(8, 60, 304, 200, 0, 500 ); } void display(void) { DrawSync(0); VSync(0); PutDispEnv(&disp[db]); PutDrawEnv(&draw[db]); db = !db; } // Audio initialisation & functions void initSnd(void){ SpuInitMalloc(MALLOC_MAX, spu_malloc_rec); // Maximum number of blocks, mem. management table address. commonAttributes.mask = (SPU_COMMON_MVOLL | SPU_COMMON_MVOLR); // Mask which attributes to set commonAttributes.mvol.left = 0x3fff; // Master volume left commonAttributes.mvol.right = 0x3fff; // see libref47.pdf, p.1058 SpuSetCommonAttr(&commonAttributes); // set attributes SpuSetIRQ(SPU_OFF); } u_long sendVAGtoRAM(unsigned int VAG_data_size, unsigned char *VAG_data){ u_long size; SpuSetTransferMode(SpuTransByDMA); // DMA transfer; can do other processing during transfer size = SpuWrite (VAG_data + sizeof(VAGhdr), VAG_data_size); // transfer VAG_data_size bytes from VAG_data address to sound buffer SpuIsTransferCompleted (SPU_TRANSFER_WAIT); // Checks whether transfer is completed and waits for completion return size; } void setVoiceAttr(unsigned int pitch, long channel, unsigned long soundAddr ){ voiceAttributes.mask= //~ Attributes (bit string, 1 bit per attribute) ( SPU_VOICE_VOLL | SPU_VOICE_VOLR | SPU_VOICE_PITCH | SPU_VOICE_WDSA | SPU_VOICE_ADSR_AMODE | SPU_VOICE_ADSR_SMODE | SPU_VOICE_ADSR_RMODE | SPU_VOICE_ADSR_AR | SPU_VOICE_ADSR_DR | SPU_VOICE_ADSR_SR | SPU_VOICE_ADSR_RR | SPU_VOICE_ADSR_SL ); voiceAttributes.voice = channel; //~ Voice (low 24 bits are a bit string, 1 bit per voice ) voiceAttributes.volume.left = 0x1000; //~ Volume voiceAttributes.volume.right = 0x1000; //~ Volume voiceAttributes.pitch = pitch; //~ Interval (set pitch) voiceAttributes.addr = soundAddr; //~ Waveform data start address voiceAttributes.a_mode = SPU_VOICE_LINEARIncN; //~ Attack rate mode = Linear Increase - see libref47.pdf p.1091 voiceAttributes.s_mode = SPU_VOICE_LINEARIncN; //~ Sustain rate mode = Linear Increase voiceAttributes.r_mode = SPU_VOICE_LINEARDecN; //~ Release rate mode = Linear Decrease voiceAttributes.ar = 0x0; //~ Attack rate voiceAttributes.dr = 0x0; //~ Decay rate voiceAttributes.rr = 0x0; //~ Release rate voiceAttributes.sr = 0x0; //~ Sustain rate voiceAttributes.sl = 0xf; //~ Sustain level SpuSetVoiceAttr(&voiceAttributes); // set attributes } void playSFX(void){ SpuSetKey(SpuOn,SPU_0CH); // Set several channels by ORing each channel bit ; ex : SpuSetKey(SpuOn,SPU_0CH | SPU_3CH | SPU_8CH); channels 0, 3, 8 are on. } int main(void) { short counter = 0; const VAGhdr * VAGfileHeader = (VAGhdr *) _binary____VAG_hello_poly_vag_start; // get header of VAG file // From libover47.pdf : // The sampling frequency of the original audio file can be used to determine the pitch // at which to play the VAG. pitch = (sampling frequency << 12)/44100L // Ex: 44.1kHz=0x1000 22.05kHz=0x800 etc unsigned int pitch = (SWAP_ENDIAN32(VAGfileHeader->samplingFrequency) << 12) / 44100L; SpuInit(); // Initialize SPU. Called only once. initSnd(); //~ // First VAG vag_spu_address = SpuMalloc(SWAP_ENDIAN32(VAGfileHeader->dataSize)); // Allocate an area of dataSize bytes in the sound buffer. spu_start_address = SpuSetTransferStartAddr(vag_spu_address); // Sets a starting address in the sound buffer get_start_addr = SpuGetTransferStartAddr(); // SpuGetTransferStartAddr() returns current sound buffer transfer start address. transSize = sendVAGtoRAM(SWAP_ENDIAN32(VAGfileHeader->dataSize), _binary____VAG_hello_poly_vag_start); // set VAG to channel setVoiceAttr(pitch, SPU_0CH, vag_spu_address); initGraph(); while (1) { if(!counter){ playSFX(); counter = 180; } FntPrint("\nPitch : %08x-%dKhz", pitch, (SWAP_ENDIAN32(VAGfileHeader->samplingFrequency)) ); FntPrint("\nSet Start addr : %08x", vag_spu_address); FntPrint("\nReturn start addr : %08x", spu_start_address); FntPrint("\nGet Start addr : %08x", get_start_addr); FntPrint("\nSend size : %08x", SWAP_ENDIAN32(VAGfileHeader->dataSize)); FntPrint("\nReturn size : %08x\n", transSize); FntPrint("\nCounter : %d\n", counter); FntFlush(-1); counter --; display(); } return 0; } ================================================ FILE: hello_world/Makefile ================================================ TARGET = hello_world SRCS = hello_world.c \ include ../common.mk ================================================ FILE: hello_world/hello_world.c ================================================ // Demonstrate DISP/DRAW env, font setup, and display a text. // Schnappy 2020 // Based on Lameguy64 tutorial : http://lameguy64.net/svn/pstutorials/chapter1/1-display.html #include #include #include #include #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 // Screen height #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 0 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 7 // Text Field Height DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; short db = 0; // index of which buffer is used, values 0, 1 void init(void) { ResetGraph(0); // Initialize drawing engine with a complete reset (0) SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1] SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1] SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1] SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1] if (VMODE) // PAL { SetVideoMode(MODE_PAL); disp[0].screen.y += 8; // add offset : 240 + 8 + 8 = 256 disp[1].screen.y += 8; } SetDispMask(1); // Display on screen setRGB0(&draw[0], 50, 50, 50); // set color for first draw area setRGB0(&draw[1], 50, 50, 50); // set color for second draw area draw[0].isbg = 1; // set mask for draw areas. 1 means repainting the area with the RGB color each frame draw[1].isbg = 1; PutDispEnv(&disp[db]); // set the disp and draw environnments PutDrawEnv(&draw[db]); FntLoad(960, 0); // Load font to vram at 960,0(+128) FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars } void display(void) { DrawSync(0); // Wait for all drawing to terminate VSync(0); // Wait for the next vertical blank PutDispEnv(&disp[db]); // set alternate disp and draw environnments PutDrawEnv(&draw[db]); db = !db; // flip db value (0 or 1) } int main(void) { init(); // execute init() while (1) // infinite loop { FntPrint("Hello world !"); // Send string to print stream FntFlush(-1); // Draw printe stream display(); // Execute display() } return 0; } ================================================ FILE: hello_xa/Makefile ================================================ .PHONY: all cleansub all: mkpsxiso -y ./isoconfig.xml cleansub: $(MAKE) clean rm -f hello_xa.cue hello_xa.bin TARGET = hello_xa SRCS = hello_xa.c \ include ../common.mk ================================================ FILE: hello_xa/README.md ================================================ See here for more informations about the XA fileformat and tools : https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/XA ================================================ FILE: hello_xa/hello_xa.c ================================================ // XA track playback example // base on `psyq/addons/scee/CD/XAPLAYER` // Refs : http://psx.arthus.net/code/XA/XATUT.pdf // http://psx.arthus.net/code/XA/xatut.zip // http://psx.arthus.net/code/XA/XA%20ADPCM%20documentation.txt // Schnappy 2021 #include #include #include #include #include // CD library #include // SPU library #include #define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL #define SCREENXRES 320 // Screen width #define SCREENYRES 240 + (VMODE << 4) // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256 #define CENTERX SCREENXRES/2 // Center of screen on x #define CENTERY SCREENYRES/2 // Center of screen on y #define MARGINX 0 // margins for text display #define MARGINY 32 #define FONTSIZE 8 * 7 // Text Field Height DISPENV disp[2]; // Double buffered DISPENV and DRAWENV DRAWENV draw[2]; short db = 0; // index of which buffer is used, values 0, 1 // SPU attributes SpuCommonAttr spuSettings; #define CD_SECTOR_SIZE 2048 // XA // Sector offset for XA data 4: simple speed, 8: double speed #define XA_SECTOR_OFFSET 4 // Number of XA files #define XA_TRACKS 1 typedef struct { int start; int end; } XA_TRACK; // Declare an array of XA_TRACK XA_TRACK XATrack[XA_TRACKS]; // Name of file to load static char * loadXA = "\\INTER8.XA;1"; CdlFILE XAPos = {0}; // Start and end position of XA data, in sectors static int StartPos, EndPos; // Current pos in file static int CurPos = -1; // Playback status : 0 not playing, 1 playing static int gPlaying = 0; // Current XA channel static char channel = 0; void init(void) { ResetGraph(0); // Initialize drawing engine with a complete reset (0) SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1] SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1] SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1] SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1] // Set video mode if (VMODE){ SetVideoMode(MODE_PAL);} SetDispMask(1); // Display on screen setRGB0(&draw[0], 50, 50, 50); // set color for first draw area setRGB0(&draw[1], 50, 50, 50); // set color for second draw area draw[0].isbg = 1; // set mask for draw areas. 1 means repainting the area with the RGB color each frame draw[1].isbg = 1; PutDispEnv(&disp[db]); // set the disp and draw environnments PutDrawEnv(&draw[db]); FntLoad(960, 0); // Load font to vram at 960,0(+128) FntOpen(MARGINX, SCREENYRES - MARGINY - FONTSIZE, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 280 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars } void display(void) { DrawSync(0); // Wait for all drawing to terminate VSync(0); // Wait for the next vertical blank PutDispEnv(&disp[db]); // set alternate disp and draw environnments PutDrawEnv(&draw[db]); db = !db; // flip db value (0 or 1) } int main(void) { // Init display init(); // SPU setup // Init Spu SpuInit(); // Set master & CD volume to max spuSettings.mask = (SPU_COMMON_MVOLL | SPU_COMMON_MVOLR | SPU_COMMON_CDVOLL | SPU_COMMON_CDVOLR | SPU_COMMON_CDMIX); spuSettings.mvol.left = 0x6000; spuSettings.mvol.right = 0x6000; spuSettings.cd.volume.left = 0x6000; spuSettings.cd.volume.right = 0x6000; // Enable CD input ON spuSettings.cd.mix = SPU_ON; // Apply settings SpuSetCommonAttr(&spuSettings); // Set transfer mode SpuSetTransferMode(SPU_TRANSFER_BY_DMA); // Init CD system CdInit(); // Optional : Set CD Attenuation volume // // CdlATV cd_vol; // cd_vol.val0 = cd_vol.val1 = cd_vol.val2 = cd_vol.val3 = 0x40; // CdMix(&cd_vol); // // Load XA file from cd // Find XA file pos CdSearchFile( &XAPos, loadXA); XATrack[0].start = CdPosToInt(&XAPos.pos); XATrack[0].end = XATrack[0].start + (XAPos.size/CD_SECTOR_SIZE) - 1; StartPos = XATrack[0].start; EndPos = XATrack[0].end; // XA setup u_char param[4]; // ORing the parameters we need to set ; drive speed, ADPCM play, Subheader filter, sector size // If using CdlModeSpeed(Double speed), you need to load an XA file that has 8 channels. // In single speed, a 4 channels XA is to be used. param[0] = CdlModeSpeed|CdlModeRT|CdlModeSF|CdlModeSize1; // Issue primitive command to CD-ROM system (Blocking-type) // Set the parameters above CdControlB(CdlSetmode, param, 0); // Pause at current pos CdControlF(CdlPause,0); // Set filter // This specifies the file and channel number to actually read data from. CdlFILTER filter; // Use file 1, channel 0 filter.file = 1; filter.chan = channel; // Set filter CdControlF(CdlSetfilter, (u_char *)&filter); // Position of file on CD CdlLOC loc; // Set CurPos to StartPos CurPos = StartPos; while (1) // infinite loop { // Begin XA file playback if (gPlaying == 0 && CurPos == StartPos){ // Convert sector number to CD position in min/second/frame and set CdlLOC accordingly. CdIntToPos(StartPos, &loc); // Send CDROM read command CdControlF(CdlReadS, (u_char *)&loc); // Set playing flag gPlaying = 1; } // When endPos is reached, set playing flag to 0 if ((CurPos += XA_SECTOR_OFFSET) >= EndPos){ gPlaying = 0; } // If XA file end is reached, stop playback if ( gPlaying == 0 && CurPos >= EndPos ){ // Stop XA playback // Stop CD playback CdControlF(CdlStop,0); // Optional // Reset parameters // param[0] = CdlModeSpeed; // Set CD mode // CdControlB(CdlSetmode, param, 0); // Switch to next channel and start play back channel = !channel; filter.chan = channel; // Set filter CdControlF(CdlSetfilter, (u_char *)&filter); CurPos = StartPos; } FntPrint("Hello XA ! %d\n", VSync(-1)); FntPrint("Start, End Pos: %d %d\n", StartPos, EndPos); FntPrint("Current Pos: %d\n", CurPos ); FntPrint("Playback status: %d\n", gPlaying ); FntFlush(-1); // Draw printe stream display(); // Execute display() } return 0; } ================================================ FILE: hello_xa/isoconfig.xml ================================================ ================================================ FILE: hello_xa/system.cnf ================================================ BOOT=cdrom:\SCES_313.37;1 TCB=4 EVENT=10 STACK=801FFFF0 ================================================ FILE: hello_xa/xa/interleave4.txt ================================================ 1 xa xa/funk.xa 1 0 1 xa xa/beach.xa 1 1 1 null 1 null ================================================ FILE: hello_xa/xa/interleave8.txt ================================================ 1 xa xa/funk.xa 1 0 1 xa xa/beach.xa 1 1 1 null 1 null 1 null 1 null 1 null 1 null ================================================ FILE: includes/CPUMAC.H ================================================ /* ** cpumac.h mike acton */ // cpu_ldr(cpu register,data pointer) // copy 32bit data from dp to r #define cpu_ldr(r,dp)\ asm(\ "lw %0, 0(%1);"\ : "=r" (r)\ : "r" (dp)\ ) // cpu_gted0(cpu register) // copy 32bit data from r to gte register 0 #define cpu_gted0(r)\ asm(\ "mtc2 %0, $0;"\ :\ : "r" (r)\ ) // cpu_gted1(cpu register) // copy 32bit data from r to gte register 1 #define cpu_gted1(r)\ asm(\ "mtc2 %0, $1;"\ :\ : "r" (r)\ ) // cpu_gted2(cpu register) // copy 32bit data from r to gte register 2 #define cpu_gted2(r)\ asm(\ "mtc2 %0, $2;"\ :\ : "r" (r)\ ) // cpu_gted3(cpu register) // copy 32bit data from r to gte register 3 #define cpu_gted3(r)\ asm(\ "mtc2 %0, $3;"\ :\ : "r" (r)\ ) // cpu_gted4(cpu register) // copy 32bit data from r to gte register 4 #define cpu_gted4(r)\ asm(\ "mtc2 %0, $4;"\ :\ : "r" (r)\ ) // cpu_gted5(cpu register) // copy 32bit data from r to gte register 5 #define cpu_gted5(r)\ asm(\ "mtc2 %0, $5;"\ :\ : "r" (r)\ )