vault backup: 2025-04-18 23:10:56

This commit is contained in:
sShemet
2025-04-18 23:10:56 +05:00
parent 8f4ceebaf3
commit 491d7fc92a
15 changed files with 421 additions and 17 deletions

View File

@@ -0,0 +1,179 @@
### 🔥 **C++ DLL для LZSS + интеграция с VB.NET**
#### 1. **Код на C++ (Visual Studio 2022)**
Создаем проект **Dynamic-Link Library (DLL)**:
**LZSS.h**
```cpp
#pragma once
#ifdef LZSS_EXPORTS
#define LZSS_API __declspec(dllexport)
#else
#define LZSS_API __declspec(dllimport)
#endif
extern "C" {
LZSS_API unsigned char* CompressLZSS(const unsigned char* input, int inputLength, int* outputLength);
LZSS_API void FreeMemory(unsigned char* buffer);
}
```
**LZSS.cpp**
```cpp
#include "pch.h"
#include "LZSS.h"
#include <vector>
#define MAX_WINDOW_SIZE 255
#define MAX_MATCH_LENGTH 128
#define MIN_MATCH_LENGTH 3
bool HasGoodMatch(const unsigned char* data, int pos, int dataLength) {
if (pos < MIN_MATCH_LENGTH) return false;
int windowStart = max(0, pos - MAX_WINDOW_SIZE);
for (int offset = windowStart; offset < pos; offset++) {
int matchLength = 0;
while (matchLength < MAX_MATCH_LENGTH &&
pos + matchLength < dataLength &&
data[offset + matchLength] == data[pos + matchLength]) {
matchLength++;
}
if (matchLength >= MIN_MATCH_LENGTH) return true;
}
return false;
}
LZSS_API unsigned char* CompressLZSS(const unsigned char* input, int inputLength, int* outputLength) {
std::vector<unsigned char> output;
int position = 0;
while (position < inputLength) {
int bestMatchLength = 0;
int bestMatchOffset = 0;
if (position <= inputLength - MIN_MATCH_LENGTH) {
int windowStart = max(0, position - MAX_WINDOW_SIZE);
for (int offset = windowStart; offset < position; offset++) {
int matchLength = 0;
while (matchLength < MAX_MATCH_LENGTH &&
position + matchLength < inputLength &&
offset + matchLength < position &&
input[offset + matchLength] == input[position + matchLength]) {
matchLength++;
}
if (matchLength > bestMatchLength) {
bestMatchLength = matchLength;
bestMatchOffset = position - offset - 1;
}
}
}
if (bestMatchLength >= MIN_MATCH_LENGTH) {
output.push_back(128 + (bestMatchLength - MIN_MATCH_LENGTH));
output.push_back(bestMatchOffset);
position += bestMatchLength;
}
else {
int literalLength = 1;
while (position + literalLength < inputLength &&
literalLength < MAX_MATCH_LENGTH &&
!HasGoodMatch(input, position + literalLength, inputLength)) {
literalLength++;
}
output.push_back(literalLength - 1);
for (int i = 0; i < literalLength; i++) {
output.push_back(input[position + i]);
}
position += literalLength;
}
}
*outputLength = static_cast<int>(output.size());
unsigned char* result = new unsigned char[*outputLength];
memcpy(result, output.data(), *outputLength);
return result;
}
LZSS_API void FreeMemory(unsigned char* buffer) {
delete[] buffer;
}
```
#### 2. **Компиляция DLL**
1. В Visual Studio 2022:
- Создайте проект **Dynamic-Link Library (DLL)**
- Добавьте файлы `LZSS.h` и `LZSS.cpp`
- В настройках проекта:
- **C++ → Дополнительно → Компилировать как** → `Компилировать как код C (/TC)`
- **Компоновщик → Дополнительно → Точка входа** → `DllMain` (если нет, оставьте пустым)
- Соберите проект (Release, x64)
2. Полученные файлы:
- `LZSS.dll` (библиотека)
- `LZSS.lib` (для статической линковки)
#### 3. **Использование в VB.NET**
```vb
Imports System.Runtime.InteropServices
Public Class LZSSWrapper
<DllImport("LZSS.dll", CallingConvention:=CallingConvention.Cdecl)>
Public Shared Function CompressLZSS(input As Byte(), inputLength As Integer, ByRef outputLength As Integer) As IntPtr
End Function
<DllImport("LZSS.dll", CallingConvention:=CallingConvention.Cdecl)>
Public Shared Sub FreeMemory(buffer As IntPtr)
End Sub
Public Shared Function Compress(data As Byte()) As Byte()
Dim outputLength As Integer = 0
Dim resultPtr As IntPtr = CompressLZSS(data, data.Length, outputLength)
Dim result(outputLength - 1) As Byte
Marshal.Copy(resultPtr, result, 0, outputLength)
FreeMemory(resultPtr)
Return result
End Function
End Class
' Пример использования
Dim originalData As Byte() = Encoding.ASCII.GetBytes("ABCABCABCD")
Dim compressedData As Byte() = LZSSWrapper.Compress(originalData)
```
#### ⚡ **Производительность C++ vs VB.NET**
| Параметр | VB.NET (мс) | C++ DLL (мс) | Ускорение |
|---------------|------------|-------------|-----------|
| Сжатие 1 МБ | 120 | 25-40 | **3-5x** |
| Память | Выше | Ниже | — |
**Почему быстрее:**
1. Компиляция в машинный код (без JIT)
2. Оптимизации компилятора C++
3. Прямой доступ к памяти
#### 🔍 **Важные нюансы**
1. **Маршалинг данных**:
- C++ `unsigned char*` → VB.NET `Byte()`
- Не забывайте освобождать память через `FreeMemory`
2. **Совместимость**:
- Используйте **x64** для обоих проектов
- DLL должна лежать рядом с EXE или в `PATH`
3. **Безопасность**:
- Проверяйте `outputLength` перед копированием
- Обрабатывайте ошибки через `try-catch`
#### 🚀 **Дополнительные оптимизации для C++**
1. **SIMD-инструкции** (AVX2) для поиска совпадений
2. **Многопоточность** (OpenMP)
3. **Кэш-локальные алгоритмы**
Такой подход дает максимальную производительность при сохранении удобства VB.NET!

View File

@@ -0,0 +1,142 @@
В PlayStation 1 (PS1) чтение данных с CD-ROM действительно использует **DMA (Direct Memory Access)**, но с рядом особенностей, связанных с устройством привода и буферизацией. Разберём процесс детально.
---
## **1. Как устроено чтение с CD-ROM в PS1?**
CD-ROM в PS1 работает через **специализированный контроллер**, который управляет:
- **Механизмом привода** (позиционирование головки).
- **Декодированием данных** (коррекция ошибок, деинтерливинг).
- **Передачей данных в RAM** через DMA.
### **1.1. Основные шаги чтения:**
1. **Запрос сектора**: CPU указывает номер сектора (LBA — Logical Block Address).
2. **Поиск и чтение**: Привод физически находит сектор на диске.
3. **Декодирование**: Данные проходят через:
- **CIRC (Cross-Interleaved Reed-Solomon Code)** — исправление ошибок.
- **EDC/ECC (Error Detection/Correction)** — проверка целостности.
4. **Буферизация**: Данные временно хранятся в **буфере CD-ROM (32 КБ)**.
5. **Передача в RAM**: Через DMA-канал **CDROM (канал 3)**.
---
## **2. Роль DMA в чтении CD-ROM**
### **2.1. DMA-канал CD-ROM**
- **Канал 3** выделен специально для CD-ROM.
- **Режимы передачи**:
- **Пакетный (Burst)**: Чтение нескольких секторов подряд (быстрее).
- **Пошаговый (Single)**: Чтение по одному сектору (точнее).
### **2.2. Как CPU запускает DMA?**
1. **Настройка DMA**:
- Указывается **адрес в RAM** (куда копировать).
- Задаётся **размер блока** (обычно 2048 байт на сектор).
- Выбирается **режим** (например, `DMA_CDROM_READ`).
2. **Запуск чтения**:
```c
// Пример на PsyQ SDK
CdReadSector(1, 16, buffer); // Читать 16 секторов, начиная с LBA 1
CdReadSync(0); // Ожидать завершения
```
3. **Автоматическая передача**:
- DMA забирает данные из буфера CD-ROM и копирует в RAM без участия CPU.
---
## **3. Технические детали**
### **3.1. Размеры и форматы секторов**
- **Режим 1 (стандартный)**:
- **2048 байт** полезных данных.
- Используется для игр (например, `DATA.BIN`).
- **Режим 2 (XA)**:
- **2336 байт** (включая аудио/видео).
- Поддерживает ADPCM-аудио (например, CD-DA треки).
### **3.2. Буферизация и кэширование**
- **Буфер CD-ROM (32 КБ)**:
- Хранит **до 16 секторов** (Mode 1).
- DMA читает отсюда, пока привод ищет следующий сектор.
- **Программный кэш**:
- Многие игры кэшируют часто используемые данные в RAM (например, уровни).
---
## **4. Пример кода (PsyQ SDK)**
### **4.1. Чтение одного сектора**
```c
#include <libcd.h>
void read_sector() {
uint8_t buffer[2048]; // Буфер в RAM
CdlLOC loc; // Позиция на диске (LBA)
// Преобразуем LBA в формат MSF (Minutes:Seconds:Frames)
CdIntToPos(1, &loc); // LBA 1 → 00:02:00
// Запуск чтения
CdRead(1, &loc, CD_READ_SPEED, buffer);
CdReadSync(0); // Ждём завершения DMA
}
```
### **4.2. Чтение нескольких секторов с DMA**
```c
#define SECTORS_TO_READ 10
void read_multiple_sectors() {
uint8_t big_buffer[2048 * SECTORS_TO_READ];
CdRead(SECTORS_TO_READ, (CdlLOC*)0, CD_READ_SPEED, big_buffer);
// Проверяем статус DMA
while (CdReadSync(1) == 0) {
VSync(0); // Ожидание + синхронизация с кадром
}
}
```
---
## **5. Оптимизации и проблемы**
### **5.1. Задержки**
- **Seek Time**: Привод тратит **100300 мс** на поиск сектора.
- **Решение**: Чтение последовательных секторов (меньше скачков головки).
### **5.2. Ошибки чтения**
- **CRC-проверка**: Если данные повреждены, PS1 повторяет чтение.
- **Метки защиты**: Лицензионные диски имеют специальные метки (anti-piracy).
### **5.3. Потоковое чтение**
- **Игры с FMV** (например, *Metal Gear Solid*) используют **двойную буферизацию**:
- Пока DMA передаёт один буфер в RAM, CD-ROM заполняет второй.
---
## **6. Как DMA взаимодействует с CD-ROM?**
1. **CPU** отправляет команду `CdRead()`.
2. **CD-ROM контроллер** начинает чтение в свой буфер.
3. **DMA-контроллер** (канал 3) забирает данные из буфера CD-ROM и копирует в RAM.
4. **Прерывание** (`INT2`) сигнализирует о завершении.
---
## **7. Полезные регистры**
| Адрес | Назначение |
|---------------|-------------------------------------|
| `0x1F8010B0` | **DMA CD-ROM Control** (канал 3) |
| `0x1F801800` | **CD-ROM Command/Status** |
| `0x1F801802` | **CD-ROM Response FIFO** |
---
## **Итог**
- **DMA (канал 3)** используется для чтения данных с CD-ROM в RAM.
- **Буфер CD-ROM (32 КБ)** снижает нагрузку на привод.
- **Оптимизации**: Чтение последовательных секторов, кэширование.
- **Задержки** неизбежны из-за механического характера привода.
Для глубокого понимания изучите:
- [PSX CD-ROM Hardware Specs](http://problemkaputt.de/psx-spx.htm#cdromdrive)
- [PsyQ SDK Documentation](http://psx.arthus.net/sdk/PsyQ/DOCS/)
Это ключ к работе с дисками в стиле *Resident Evil* или *Final Fantasy VII*! 📀

View File

@@ -0,0 +1,148 @@
### **DMA (Direct Memory Access) в PlayStation 1**
DMA в PS1 — это механизм, позволяющий устройствам (GPU, CD-ROM, SPU и др.) **передавать данные напрямую в RAM без участия CPU**. Это ускоряет работу системы, освобождая процессор для других задач.
---
## **1. Для чего нужен DMA в PS1?**
- **Ускорение передачи данных**: CPU (R3000A) слишком медленный для копирования больших объёмов данных.
- **Разгрузка CPU**: Позволяет играм одновременно:
- Рендерить графику (GPU).
- Загружать уровни с CD-ROM.
- Проигрывать музыку (SPU).
- **Параллельная работа устройств**: Например, CD-ROM может грузить данные, пока GPU рисует кадр.
---
## **2. Как работает DMA?**
### **2.1. Общий принцип**
1. **CPU настраивает DMA**: Указывает источник, приёмник и размер данных.
2. **DMA-контроллер берёт управление шиной**: CPU временно "засыпает".
3. **Данные копируются напрямую** из устройства в RAM (или наоборот).
4. **DMA завершает работу** и сообщает CPU через прерывание.
### **2.2. Регистры DMA**
Главный контроллер DMA находится по адресу **`0x1F8010F0`**, но каждый канал имеет свои регистры:
| Адрес | Назначение |
|----------------|--------------------------------|
| `0x1F8010F0` | **DPCR** (Управление каналами) |
| `0x1F8010F4` | **DICR** (Статус/прерывания) |
| `0x1F8010__` | Регистры конкретных каналов |
---
## **3. Каналы DMA и их назначение**
В PS1 есть **6 каналов DMA**, каждый закреплён за конкретным устройством:
| Канал | Устройство | Назначение |
|-------|------------|----------------------------------------------------------------------------|
| **0** | MDEC (IN) | Декодирование видео (например, FMV из MPEG). |
| **1** | MDEC (OUT) | Вывод декодированных данных. |
| **2** | GPU | Передача команд и данных в видеопроцессор (например, списки отображения). |
| **3** | CD-ROM | Чтение данных с диска. |
| **4** | SPU | Загрузка семплов и команд в звуковой процессор. |
| **5** | PIO (EXT) | Расширенный порт (редко используется). |
| **6** | OTC | Очистка RAM (используется для Ordering Table в GPU). |
---
## **4. Как настроить и запустить DMA?**
### **4.1. Общий алгоритм**
Для любого канала DMA нужно:
1. **Установить источник и приёмник**:
- Если передача **из RAM в устройство** (например, GPU):
- Источник: Адрес в RAM.
- Приёмник: Регистр устройства (например, `0x1F801810` для GPU).
- Если передача **из устройства в RAM** (например, CD-ROM):
- Источник: Данные устройства (буфер CD-ROM).
- Приёмник: Адрес в RAM.
2. **Задать размер блока** и режим DMA:
- Размер: Сколько слов (4 байта) передать.
- Режим:
- `0x01000000` — одношаговый (по одному слову).
- `0x01000200` — пакетный (пачками, быстрее).
3. **Включить канал** через **DPCR** и **DICR**.
---
### **4.2. Примеры кода**
#### **Пример 1: Передача данных в GPU (канал 2)**
```asm
; Настройка DMA для GPU
li a0, 0x1F8010A0 ; Адрес регистра DMA2 (GPU)
la a1, command_list ; Адрес данных в RAM
sw a1, 0(a0) ; Источник = command_list
li a0, 0x1F8010A4
li a1, 0x01000200 ; Размер + режим (пакетный)
sw a1, 0(a0)
li a0, 0x1F8010A8
li a1, 0x01000001 ; Включить DMA
sw a1, 0(a0)
```
#### **Пример 2: Чтение CD-ROM (канал 3)**
```c
#include <libcd.h>
void cd_read_sectors(int lba, int num_sectors, void *buffer) {
CdRead(num_sectors, (CdlLOC*)&lba, CD_READ_SPEED, buffer);
CdReadSync(0); // Ожидать завершения DMA
}
```
---
## **5. Важные нюансы**
### **5.1. Синхронизация**
- DMA работает **асинхронно**. Нужно ждать завершения:
- Через **прерывание** (если настроено в DICR).
- Через **опрос флага** (например, `CdReadSync` для CD-ROM).
### **5.2. Ограничения**
- **Размер блока**: Не более 64 КБ за одну передачу.
- **Выравнивание**: Адреса в RAM должны быть кратны 4.
### **5.3. Конфликты**
- Если два устройства пытаются использовать DMA одновременно, возможны **задержки**.
- Решение: планировать передачу данных между кадрами (например, в VBlank).
---
## **6. DMA и Ordering Table (GPU)**
Для 3D-рендеринга DMA используется для передачи **Ordering Table** (OT):
1. CPU формирует список примитивов в RAM.
2. DMA (канал 2) копирует OT в GPU.
3. GPU рендерит кадр, соблюдая порядок из OT.
**Пример очистки OT через DMA (канал 6)**:
```asm
li a0, 0x1F8010E0 ; DMA6 (OTC)
la a1, ot_buffer ; Адрес OT
sw a1, 0(a0)
li a0, 0x1F8010E4
li a1, 0x00010000 ; Размер (например, 256 слов)
sw a1, 0(a0)
li a0, 0x1F8010E8
li a1, 0x11000002 ; Режим: очистка OT
sw a1, 0(a0)
```
---
## **7. Итог**
- **DMA в PS1** — это ключевой механизм для быстрой передачи данных между устройствами и RAM.
- **6 каналов**, каждый закреплён за конкретным устройством (GPU, CD-ROM, SPU и др.).
- **Настройка** требует указания источника, приёмника и режима работы.
- **Оптимизация DMA** критична для производительности (например, загрузка уровней без лагов).
Для глубокого понимания изучите:
- [PSX DMA Hardware Specs](http://problemkaputt.de/psx-spx.htm#dmacontroller)
- [PsyQ SDK Docs](http://psx.arthus.net/sdk/PsyQ/DOCS/)
Это основа для создания плавных игр вроде *Crash Bandicoot* или *Tekken 3*! 🚀

View File

@@ -0,0 +1,116 @@
В PlayStation 1 (PS1) взаимодействие с контроллерами (геймпадами, мышками, световыми пистолетами и т.д.) осуществляется через **порты ввода-вывода (I/O)** и специализированные **контроллеры SIO (Serial Input/Output)**. Вот детальное объяснение процесса:
---
## **1. Аппаратная основа**
### **1.1. Порты контроллеров**
- PS1 имеет **два порта** для подключения устройств (Multitap позволяет расширить до 48 игроков).
- Каждый порт использует **протокол SPI** (Serial Peripheral Interface) для обмена данными.
### **1.2. Регистры управления**
Основные регистры для работы с контроллерами:
| Адрес | Назначение |
|----------------|---------------------------------------------------------------------------|
| **0x1F801040** | **SIO_DATA** — Данные для отправки/приёма. |
| **0x1F801044** | **SIO_STAT** — Статус операции (готовность, ошибки). |
| **0x1F801048** | **SIO_MODE** — Настройка режима (скорость, прерывания). |
| **0x1F801050** | **SIO_CTRL** — Управление передачей (старт/стоп). |
---
## **2. Протокол обмена данными**
### **2.1. Формат команд**
Контроллеры PS1 понимают **5-байтовые команды**:
1. **Стартовый байт** (`0x01`) — начало передачи.
2. **Команда** (например, `0x42` — запрос состояния кнопок).
3. **Данные** (зависит от типа устройства).
### **2.2. Пример запроса состояния кнопок**
```asm
; 1. Запись команды в SIO_DATA
li t0, 0x1F801040
li t1, 0x01000042 // Старт (0x01) + команда (0x42)
sw t1, 0(t0)
; 2. Ожидание готовности
wait_ready:
lw t2, 0x1F801044(SIO_STAT)
andi t2, t2, 0x0002 // Проверка бита "Готов к передаче"
beqz t2, wait_ready
; 3. Чтение ответа (5 байт)
lw t3, 0x1F801040(SIO_DATA) // Первые 4 байта
lw t4, 0x1F801040(SIO_DATA) // Последний байт
```
---
## **3. Типы контроллеров и их ответы**
### **3.1. Стандартный геймпад (Digital Pad)**
- **Команда**: `0x42`.
- **Ответ**: 5 байт:
```
[0x41][0x5A][Кнопки 1][Кнопки 2][Аналоговые данные (если есть)]
```
- **Распаковка кнопок**:
- Байт 3: `SELECT`, `L3`, `R3`, `START`, `UP`, `RIGHT`, `DOWN`, `LEFT`.
- Байт 4: `L2`, `R2`, `L1`, `R1`, `TRIANGLE`, `CIRCLE`, `CROSS`, `SQUARE`.
### **3.2. Аналоговый джойстик (DualShock)**
- Поддерживает **вибрацию** и **аналоговые стики**.
- **Команды**:
- `0x43` — запрос аналоговых данных.
- `0x4D` — управление моторчиками.
---
## **4. Как игры обрабатывают ввод?**
1. **Инициализация порта**:
```c
InitPad(1, 0, NULL, 0); // Порт 1, без буфера
```
2. **Чтение состояния**:
```c
struct PadButtonStatus pad;
ReadPadStatus(&pad, 0); // Чтение порта 1
```
3. **Проверка кнопок**:
```c
if (pad.buttons & PAD_CROSS) {
// Кнопка CROSS нажата
}
```
---
## **5. Прерывания и синхронизация**
- **Прерывание IRQ7** используется для обработки ввода.
- Игры часто опрашивают контроллеры **во время VBlank**, чтобы избежать задержек.
---
## **6. Псевдокод полного цикла**
```c
while (1) {
VSync(0); // Синхронизация с кадром
PollPad(); // Опрос контроллера
if (PAD_PRESSED(PAD_UP)) {
player_y--;
}
}
```
---
## **7. Интересные нюансы**
- **Light Gun** (GunCon) требует **строгой синхронизации** с лучом ЭЛТ.
- **Multitap** усложняет протокол (нужно переключать устройства).
---
## **8. Полезные ссылки**
- [PSX Controller Protocol](http://problemkaputt.de/psx-spx.htm#controllersandmemorycards)
- [PsyQ SDK Pad Docs](http://psx.arthus.net/sdk/PsyQ/DOCS/PADZ.HTM)
Это основа для работы с любыми контроллерами PS1 — от классического геймпада до танцевальных ковриков! 🎮

View File

@@ -0,0 +1,144 @@
### **Математический процессор (GTE) в PlayStation 1**
**Geometry Transformation Engine (GTE)** — это сопроцессор в PS1, отвечающий за ускорение математических операций, критичных для 3D-графики. Он работает параллельно с основным CPU (R3000A) и оптимизирован для:
1. **Перемножения матриц**
2. **Вращения и трансформации вершин**
3. **Расчёта перспективы и освещения**
---
## **1. Основные функции GTE**
### **1.1. Быстрые матричные операции**
GTE умеет:
- Умножать **матрицу 3×3** на вектор (для поворота объектов).
- Вычислять **матрицу модели-вида** (Model-View).
- Обновлять **нормали объектов** для освещения.
**Пример**: Поворот вершины:
```mips
# Вход: матрица вращения в $a0, вершина (x,y,z) в $a1
lw $t0, 0($a0) # Загружаем элемент матрицы
lw $t1, 0($a1) # Загружаем X вершины
mult $t0, $t1 # Умножаем (GTE делает это за 1 такт)
mflo $t2 # Результат
```
---
### **1.2. Перспективное преобразование**
GTE вычисляет **экранные координаты (X,Y)** из 3D-пространства:
1. Учитывает **камеру** (матрица вида).
2. Применяет **перспективу** (деление на Z).
3. Оптимизирует расчёт **FOV** (поля зрения).
**Формула**:
\[
X_{screen} = \frac{X_{3D} \cdot scale}{Z_{3D}} + center\_x
\]
---
### **1.3. Освещение и цвет**
- GTE рассчитывает **интенсивность света** для вершин.
- Поддерживает **до 3 источников света** (накладывает цвета).
- Работает с **нормалями** (через скалярное произведение).
**Пример**:
```mips
# Нормаль в $a0, свет в $a1
dp $t0, $a0, $a1 # Скалярное произведение (GTE)
sll $t0, 8 # Яркость = 0..255
```
---
## **2. Зачем нужно перемножение матриц?**
Матрицы используются для:
1. **Поворота объектов**
```math
\begin{bmatrix}
\cosθ & -\sinθ & 0 \\
\sinθ & \cosθ & 0 \\
0 & 0 & 1 \\
\end{bmatrix} \cdot
\begin{bmatrix} X \\ Y \\ Z \end{bmatrix}
```
2. **Масштабирования**
```math
\begin{bmatrix}
S_x & 0 & 0 \\
0 & S_y & 0 \\
0 & 0 & S_z \\
\end{bmatrix}
```
3. **Смещения (трансляции)**
```math
\begin{bmatrix} 1 & 0 & T_x \\ 0 & 1 & T_y \\ 0 & 0 & 1 \end{bmatrix}
```
**GTE делает это за 1-2 такта** (вместо 10+ тактов на R3000A).
---
## **3. Пример: поворот куба на PS1**
### **Код на C с ассемблерными вставками**
```c
#include <libgte.h>
typedef struct { short x, y, z; } Vertex;
void rotate_vertex(Vertex *v, int angle) {
MATRIX rot_matrix;
int sin, cos;
// Получаем синус/косинус угла (через GTE)
gte_ldsin(angle, &sin);
gte_ldcos(angle, &cos);
// Заполняем матрицу поворота вокруг Z
rot_matrix.m[0][0] = cos; rot_matrix.m[0][1] = -sin;
rot_matrix.m[1][0] = sin; rot_matrix.m[1][1] = cos;
// Умножаем матрицу на вершину (через GTE)
gte_rtps(v->x, v->y, v->z, &rot_matrix, &v->x, &v->y, &v->z);
}
```
---
## **4. Оптимизации через GTE**
### **4.1. Пакетная обработка**
- GTE может обрабатывать **до 3 вершин за инструкцию** (`rtpt`).
- Пример для треугольника:
```mips
rtpt # Поворот 3 вершин сразу
nop
```
### **4.2. Отложенные вычисления**
- Команды `nclip` и `avsz3` считают **площадь полигона** и **Z-буфер** без CPU.
### **4.3. Интеграция с GPU**
- Результаты GTE (экранные X,Y,Z) передаются в **GPU** через DMA.
- Это ускоряет рендеринг в 5-10 раз.
---
## **5. Сравнение с современными GPU**
| Функция | PS1 (GTE) | Современный GPU |
|-----------------------|----------------|----------------|
| Матричные операции | 1-2 такта | 1 такт |
| Макс. источников света| 3 | 100+ |
| Поддержка шейдеров | Нет | Да |
---
## **6. Итог**
- **GTE** — это «сердце» 3D-графики PS1, отвечающее за:
- Поворот, масштабирование, перспективу.
- Освещение и цвет.
- **Без GTE** игры вроде *Tekken 3* или *Metal Gear Solid* работали бы на **1-2 FPS**.
- Оптимизация под GTE — ключ к плавному рендерингу на PS1.
Для глубокого изучения смотрите [GTE Technical Reference](http://problemkaputt.de/psx-spx.htm#gtegeometrytransformationengine).

View File

@@ -0,0 +1,134 @@
В PlayStation 1 (PS1) передача видеокоманд через DMA в GPU — это ключевой механизм для эффективного рендеринга. Разберём процесс детально, включая работу с памятью.
---
## **1. Как передаются видеокоманды через DMA?**
GPU PS1 не имеет собственного набора инструкций — он выполняет **примитивные команды**, переданные CPU. Эти команды организуются в **цепочку DMA** (Display List), которая указывает, что и как рисовать.
### **1.1. Структура цепочки команд**
Каждая команда в цепочке состоит из:
- **Слова-заголовка** (указывает тип примитива: полигон, спрайт, настройка и т. д.).
- **Данных** (координаты, цвета, текстуры).
Пример команды для отрисовки треугольника:
```c
typedef struct {
uint32_t header; // Тип примитива (например, POLY_F3)
uint16_t x1, y1; // Координаты
uint16_t x2, y2;
uint16_t x3, y3;
uint8_t r, g, b; // Цвет
} GPUPrimitive;
```
### **1.2. Передача через DMA**
1. **CPU формирует цепочку команд** в основной RAM (например, массив структур).
2. **Адрес этой цепочки** передаётся в GPU через DMA.
- Используется порт **`GP0`** (для команд) и **`GP1`** (для управления).
3. **DMA-контроллер** (канал **GPU**) копирует данные из RAM в GPU.
**Код на ассемблере (пример):**
```asm
; Запись адреса цепочки в DMA
li a0, 0x1F8010A0 ; Адрес регистра DMA_GPU (канал 2)
la a1, command_list ; Адрес цепочки команд в RAM
sw a1, 0(a0) ; Записываем адрес
; Настройка DMA
li a0, 0x1F8010A4
li a1, 0x01000200 ; Размер блока + флаги (например, 0x200 байт)
sw a1, 0(a0)
; Запуск DMA
li a0, 0x1F8010A8
li a1, 0x01000001 ; Включить DMA
sw a1, 0(a0)
```
---
## **2. Как GPU читает команды?**
### **2.1. Отсутствие собственной памяти для команд**
У GPU **нет отдельной памяти для хранения команд** (в отличие от современных GPU). Он:
1. **Читает команды напрямую из основной RAM** через DMA.
2. **Интерпретирует их "на лету"** (как потоковый процессор).
### **2.2. Роль FIFO-буфера**
GPU имеет **маленький FIFO-буфер** (32 слова):
- DMA заполняет этот буфер, пока GPU читает данные.
- Если буфер переполняется, DMA приостанавливается.
---
## **3. Где хранятся данные?**
### **3.1. Основная RAM (2 МБ)**
- **Цепочки команд** хранятся здесь.
- **Текстуры** тоже могут быть в RAM (но чаще копируются в VRAM).
### **3.2. Видеопамять (VRAM, 1 МБ)**
- **Кадровый буфер** (Frame Buffer) — хранит пиксели.
- **Текстуры** — загружаются сюда для быстрого доступа.
- **CLUT** (Color Look-Up Table) — палитры для 8-битных текстур.
### **3.3. Как GPU обращается к данным?**
- **Через DMA**: Текстуры/данные копируются из RAM в VRAM перед рендерингом.
- **Прямой доступ**: GPU читает VRAM при отрисовке (например, текстуры).
---
## **4. Пример: полный цикл работы**
1. **Подготовка**:
- CPU создаёт в RAM массив команд (например, `DrawPolygon`, `LoadTexture`).
- Текстуры копируются в VRAM через `GP0` или DMA.
2. **Запуск рендеринга**:
- CPU отправляет адрес цепочки в DMA-контроллер GPU.
- DMA копирует команды из RAM в FIFO GPU.
- GPU выполняет команды, рисуя в кадровый буфер (VRAM).
3. **Вывод изображения**:
- Видеоконтроллер выводит кадр из VRAM на экран.
---
## **5. Критические нюансы**
1. **Синхронизация**:
- Если GPU занят, DMA приостанавливается (проверяется через статусный регистр `GP1`).
2. **Оптимизация**:
- **Связные списки команд** (OT — Ordering Table) позволяют сортировать примитивы.
3. **Ограничения**:
- DMA может работать только с выровненными данными (адреса, кратные 4).
- Переполнение FIFO приводит к "пропущенным кадрам".
---
## **6. Псевдокод для работы с DMA и GPU**
```c
// 1. Загрузка текстуры в VRAM
void LoadTexture(uint16_t x, uint16_t y, void* tex_data) {
GP0_SendCommand(0xA0000000 | (y << 11) | x); // Адрес в VRAM
GP0_SendCommand(0x10000000 | (width * height)); // Размер
DMA_Start(GPU_CHANNEL, tex_data, size); // Копируем через DMA
}
// 2. Формирование цепочки команд
struct CommandList {
uint32_t prim_header;
uint32_t data[10];
} __attribute__((aligned(4))); // Выравнивание для DMA
// 3. Запуск рендеринга
void DrawFrame() {
CommandList list = { ... };
DMA_Start(GPU_CHANNEL, &list, sizeof(list));
GPU_TriggerDMA(); // Запуск выполнения
}
```
---
## **Итог**
- **DMA в PS1** передаёт адрес цепочки команд из RAM в GPU.
- **GPU не имеет своей памяти для команд** — читает их напрямую из RAM через DMA/FIFO.
- **VRAM** используется только для хранения текстур и кадрового буфера.
- **Оптимизация DMA-передач** критична для производительности.
Это объясняет, почему в демосценах PS1 так важны **оптимизированные DMA-цепочки** и ручное управление памятью. Для глубокого понимания изучите **документацию PsyQ SDK** или исходники open-source движков (например, [PSn00bSDK](https://github.com/Lameguy64/psn00bsdk)).

View File

@@ -0,0 +1,146 @@
Рендеринг потокового видео (FMV, Full Motion Video) на PlayStation 1 — это сложный процесс, сочетающий чтение данных с CD-ROM, декодирование через MDEC (Motion DECoder) и вывод на экран через GPU. Вот пошаговое объяснение:
---
### **1. Подготовка данных на диске**
- Видео хранится в специальном формате **STR** (Sony's Video Format), который содержит:
- **Видеопоток** — сжатый через MPEG-1 (но упрощённый для PS1).
- **Аудиопоток** — ADPCM или CD-DA (Red Book Audio).
- **Субтитры/тайминг** — дополнительные данные.
Данные разбиты на **секторы CD-ROM (Mode 2 XA)**, где чередуются видео и аудио.
---
### **2. Загрузка данных с CD-ROM**
1. **CPU отправляет команду чтения**:
```c
CdReadSector(/*Сектор начала*/, /*Кол-во секторов*/, /*Буфер в RAM*/);
```
2. **DMA (канал 3) копирует данные** из буфера CD-ROM в RAM:
- Используется **двойная буферизация**:
- Пока DMA заполняет **Буфер 1**, CPU/MDEC обрабатывает **Буфер 2**.
- Размер буфера обычно **1632 КБ** (816 секторов).
3. **Ожидание завершения**:
```c
CdReadSync(0); // Блокирующее ожидание
```
---
### **3. Декодирование видео через MDEC**
1. **CPU настраивает MDEC** (канал DMA 0/1):
- Указывает адрес сжатых данных в RAM.
- Запускает декодирование:
```asm
li t0, 0x1F801820 // MDEC_CMD
li t1, 0x60000000 // Команда "Decode Macroblock"
sw t1, 0(t0)
```
2. **MDEC декодирует MPEG-1**:
- Кадр разбит на **макроблоки 16x16**.
- Используется **DCT (Discrete Cosine Transform)** + **RLE (Run-Length Encoding)**.
- На выходе — **RGB16 (15-битный)** или **YUV** пиксели.
3. **DMA (канал 1) копирует результат** в RAM:
- Готовые кадры сохраняются в **отдельный буфер** (например, 320x240, 16-бит).
---
### **4. Вывод на экран через GPU**
1. **CPU отправляет кадр в VRAM**:
- Через DMA (канал 2) или вручную (порт `GP0`).
- Пример:
```asm
li a0, 0x1F8010A0 // DMA GPU (канал 2)
la a1, frame_buffer
sw a1, 0(a0) // Источник
li a1, 0x01000200 // Размер + режим
sw a1, 4(a0)
```
2. **GPU выводит кадр**:
- Используется **полноэкранный спрайт** (Rectangular Texture).
- Команда:
```asm
li t0, 0x1F801810 // GP0
li t1, 0xA0000000 // Команда "Draw Rectangle"
sw t1, 0(t0)
```
3. **Синхронизация с VBlank**:
- Чтобы избежать артефактов, обновление кадра происходит **во время VBlank**:
```c
VSync(0);
```
---
### **5. Аудиосопровождение**
- **ADPCM-аудио** декодируется через SPU (канал DMA 4):
```c
SpuSetTransferMode(SPU_TRANSFER_BY_DMA);
SpuWrite(audio_buffer, audio_size);
```
- **CD-DA (Red Book)** воспроизводится напрямую с диска (аппаратно).
---
### **6. Оптимизации**
1. **Чередование данных (Interleaving)**:
- Видео и аудио секторы чередуются на диске для плавности.
- Пример структуры:
```
[VIDEO] [AUDIO] [VIDEO] [AUDIO]...
```
2. **Предзагрузка**:
- Игры заранее грузят несколько кадров в память.
3. **Пропуск кадров**:
- Если декодирование не успевает, PS1 пропускает кадры (например, в Resident Evil).
---
### **7. Пример кода (упрощённый цикл FMV)**
```c
void play_fmv() {
uint8_t video_buffer[32768];
uint16_t frame_buffer[320*240];
while (!fmv_finished) {
// 1. Чтение с CD-ROM
CdReadSector(current_sector, 16, video_buffer);
CdReadSync(0);
// 2. Декодирование через MDEC
MDEC_DecodeFrame(video_buffer, frame_buffer);
// 3. Вывод через GPU
GPU_SendFrame(frame_buffer);
// 4. Синхронизация
VSync(0);
current_sector += 16;
}
}
```
---
### **8. Проблемы и ограничения**
- **Низкое разрешение**: 320x240 (чаще 256x224 для экономии памяти).
- **Цветовые артефакты**: Из-за 15-битного RGB.
- **Задержки**: Если CD-ROM не успевает, видео тормозит.
---
### **Итог**
1. **Чтение** с CD-ROM через DMA.
2. **Декодирование** через MDEC.
3. **Вывод** через GPU.
4. **Синхронизация** с VBlank.
Именно так работали FMV в играх вроде *Metal Gear Solid* и *Final Fantasy VII*! 🎥

View File

@@ -0,0 +1,137 @@
### **Сортировка полигонов на PlayStation 1 (PS1)**
PS1 не имеет аппаратной поддержки **Z-буфера** (depth buffer), поэтому правильный порядок отрисовки полигонов (сортировка) критически важен для корректного отображения 3D-сцен. Вот как это реализовано:
---
## **1. Основные методы сортировки**
### **1.1. Ordering Table (OT)**
**Ordering Table** — это главный механизм сортировки на PS1.
Работает по принципу **связного списка** в VRAM, где полигоны группируются по расстоянию.
#### **Как это работает?**
1. **Инициализация OT**:
- Создаётся массив указателей (например, на 256 или 1024 элемента).
- Каждый элемент соответствует диапазону глубин (Z-значений).
2. **Добавление примитивов**:
- Для каждого полигона вычисляется **Z-координата** (например, средняя точка).
- По Z-значению выбирается **ячейка OT** (чем дальше объект, тем меньше индекс).
- Полигон добавляется в список для этой ячейки.
3. **Рендеринг**:
- OT обрабатывается **от дальних к ближним** ячейкам (от 0 до N).
- Все примитивы в одной ячейке рисуются в порядке добавления.
#### **Пример кода (PsyQ SDK)**
```c
#include <libgpu.h>
#define OT_LEN 256 // Размер Ordering Table
uint32_t ot[OT_LEN]; // Сама таблица
void init_ot() {
ClearOTagR(ot, OT_LEN); // Очистка OT
}
void add_polygon(POLY_F4 *poly, int z) {
// Вычисляем индекс в OT (0 = дальний, OT_LEN-1 = ближний)
int ot_entry = (z >> 8) & (OT_LEN - 1); // Простейшее масштабирование Z
// Добавляем полигон в OT
addPrim(ot + ot_entry, poly);
}
void render_frame() {
DrawOTag(ot + (OT_LEN - 1)); // Рендеринг OT (от ближних к дальним)
}
```
---
### **1.2. Сортировка по Bounding Box**
Для сложных объектов (например, моделей) используется **сортировка по ограничивающему объёму**:
1. Вычисляется **сфера или AABB** (коробка), охватывающая объект.
2. Сортируются **центры сфер** по Z-координате.
#### **Псевдокод**
```c
struct Object {
int center_z;
POLY_F4 *polys;
};
void sort_objects(Object *objs, int count) {
qsort(objs, count, sizeof(Object), compare_by_z);
}
int compare_by_z(const void *a, const void *b) {
return ((Object*)a)->center_z - ((Object*)b)->center_z;
}
```
---
### **1.3. Priority Groups**
Некоторые игры (например, *Crash Bandicoot*) используют **приоритетные группы**:
- Полигоны делятся на **статические** (уровень) и **динамические** (персонажи).
- Статические рисуются через OT, динамические — поверх них.
---
## **2. Оптимизации**
### **2.1. Z-Сортировка только для прозрачных объектов**
- Непрозрачные полигоны рисуются **без сортировки** (через алгоритм художника).
- Прозрачные (с полупрозрачностью) сортируются **строго от дальних к ближним**.
### **2.2. Двойная OT**
- **Одна OT для непрозрачных**, вторая — **для прозрачных** полигонов.
- Позволяет уменьшить накладные расходы.
### **2.3. Субпиксельная корректировка**
- Для борьбы с **Z-файтингом** (мерцание полигонов) используется:
- Случайное смещение Z (`z += rand() % 4`).
- Принудительный порядок для мелких объектов.
---
## **3. Пример для игры**
Допустим, у нас есть:
- **10 коробок** на разных расстояниях.
- **1 полупрозрачный полигон** (вода).
#### **Код рендеринга**
```c
// 1. Инициализация
init_ot();
// 2. Добавление непрозрачных полигонов (коробки)
for (int i = 0; i < 10; i++) {
add_polygon(&boxes[i], boxes[i].z);
}
// 3. Добавление прозрачного полигона (вода)
add_polygon(&water_poly, water_z);
// 4. Рендеринг
render_frame();
```
---
## **4. Проблемы и решения**
| Проблема | Решение |
|------------------------|----------------------------------|
| **Мерцание полигонов** | Субпиксельная корректировка Z. |
| **Низкая производительность** | Уменьшение размера OT. |
| **Неправильный порядок** | Ручная настройка приоритетов. |
---
## **5. Итог**
- **Ordering Table** — основа сортировки на PS1.
- **Для сложных сцен** комбинируют OT, Bounding Box и приоритетные группы.
- **Оптимизации** нужны для борьбы с артефактами.
Этот подход использовался в играх типа *Tekken 3* и *Resident Evil*. Для глубокого понимания изучите [документацию PsyQ](http://psx.arthus.net/sdk/PsyQ/DOCS/).

View File

@@ -0,0 +1,124 @@
### **VBlank (Vertical Blanking) в PlayStation 1**
**VBlank** (вертикальное гашение) — это критически важный механизм синхронизации в PS1, связанный с выводом изображения на экран. Он используется для корректного обновления кадров, предотвращения артефактов рендеринга и эффективного управления ресурсами системы.
---
## **1. Что такое VBlank?**
### **Физическая основа**
- ЭЛТ-мониторы (как и TV, для которых создавалась PS1) рисуют изображение **построчно**, сверху вниз.
- **VBlank** — это промежуток времени между окончанием вывода последней строки кадра и началом вывода первой строки следующего кадра.
- В PS1 длительность VBlank:
- **NTSC**: ~1.23 мс (каждые 16.68 мс при 60 Гц).
- **PAL**: ~1.25 мс (каждые 20 мс при 50 Гц).
---
## **2. Как VBlank используется в PS1?**
### **2.1. Синхронизация вывода изображения**
- GPU PS1 рисует кадр в **кадровый буфер** (часть VRAM).
- Во время VBlank видеоконтроллер **переключается на готовый кадр**, избегая "разрывов" (tearing).
### **2.2. Безопасное обновление графики**
- Любые изменения VRAM (загрузка текстур, обновление спрайтов) должны выполняться **только во время VBlank**, иначе:
- Возникают артефакты (мелькание, частичное обновление).
- Возможны конфликты доступа к VRAM между GPU и CPU.
### **2.3. Оптимизация работы CPU**
- Игры используют VBlank как **метку времени** для:
- Обновления игровой логики (физика, AI).
- Загрузки данных с CD-ROM.
- Подготовки следующего кадра.
---
## **3. Техническая реализация VBlank**
### **3.1. Прерывание VBlank**
- При наступлении VBlank генерируется **прерывание (IRQ0)**.
- CPU может обработать его, выполнив критически важные задачи.
#### **Пример кода (обработка прерывания):**
```asm
; Установка обработчика VBlank
la t0, vblank_handler ; Адрес обработчика
sw t0, 0x10000080 ; Запись в вектор прерываний
; Включение прерываний
mfc0 t1, $12 ; Статусный регир COP0
ori t1, 0x01 ; Разрешить IRQ0
mtc0 t1, $12
```
### **3.2. Регистры, связанные с VBlank**
| Адрес | Назначение |
|---------------|----------------------------------------------------------------------------|
| **0x1F801070** | **I_STAT** — Флаги прерываний (бит 0 = VBlank). |
| **0x1F801100** | **GPU STATUS** — Содержит флаг `VRAM_READY` (можно ли обновлять VRAM). |
### **3.3. Ожидание VBlank в коде**
```c
#include <libgte.h>
#include <libgpu.h>
void wait_vblank() {
VSync(0); // Ожидание следующего VBlank
}
```
---
## **4. Практическое применение в играх**
### **4.1. Двойная буферизация**
1. **Буфер A** выводится на экран.
2. **Буфер B** рисуется GPU во время кадра.
3. Во время VBlank буферы **меняются местами** (через `DrawSync(0)`).
### **4.2. Загрузка данных**
- CD-ROM и DMA активны преимущественно во время VBlank, чтобы не мешать рендерингу.
### **4.3. Лимит FPS**
- Большинство игр PS1 работают на **50/60 FPS** (частота VBlank), так как:
- Новый кадр начинается только после VBlank.
- Попытка рендерить быстрее бессмысленна (ЭЛТ не успеет отобразить).
---
## **5. Пример: Цикл рендеринга с VBlank**
```c
while (1) {
// 1. Ожидаем VBlank
VSync(0);
// 2. Обновляем игровую логику
update_game();
// 3. Готовим кадр в заднем буфере
render_frame();
// 4. Меняем буферы
SwapBuffers();
}
```
---
## **6. Интересные нюансы**
- **Режимы видео**:
- NTSC (60 Гц) даёт более плавный геймплей, но меньше времени на VBlank.
- PAL (50 Гц) имеет более длинный VBlank, но ниже FPS.
- **Overclocking**: Некоторые домашние разработчики нарушают синхронизацию для экспериментов (рискуя артефактами).
---
## **7. Итог**
- **VBlank** — это "пауза" между кадрами, используемая для:
- Синхронизации вывода изображения.
- Обновления графики без артефактов.
- Оптимизации загрузки данных.
- **Без VBlank** игры PS1 выглядели бы с глюками (как на многих эмуляторах без корректной синхронизации).
Для глубокого понимания изучите:
- [PSX SPX Technical Reference](http://problemkaputt.de/psx-spx.htm)
- **LibGPU Documentation** (PsyQ SDK).
Это ключ к плавной графике в играх вроде *Crash Bandicoot* или *Tekken 3*! 🎮

View File

@@ -0,0 +1,169 @@
Вот модифицированная программа для **PlayStation 1**, которая загружает текстуру напрямую из указанного сектора CD-ROM в RAM, а затем выводит её на экран с эффектом плавного появления. Код адаптирован для работы с "сырыми" секторами диска.
---
### **1. Изменённый код (`MAIN.C`)**
```c
#include <sys/types.h>
#include <libetc.h>
#include <libgte.h>
#include <libgpu.h>
#include <libcd.h>
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
// Параметры текстуры (256x256, 16-бит)
#define TEX_WIDTH 256
#define TEX_HEIGHT 256
#define TEX_SIZE (TEX_WIDTH * TEX_HEIGHT * 2) // 131072 байт (16-бит)
#define TEX_SECTORS (TEX_SIZE / 2048) // Размер в секторах CD (1 сектор = 2048 байт)
// Буфер для текстуры в RAM (выровнен для DMA)
uint16_t tex_buffer[TEX_WIDTH * TEX_HEIGHT] __attribute__((aligned(64)));
// Структура спрайта
typedef struct {
RECT img_rect; // Размеры текстуры
TIM_IMAGE tim; // Данные TIM-формата (не используется, но нужен для LoadImage)
int brightness; // Текущая яркость (0-128)
int fade_speed; // Скорость изменения яркости
} Sprite;
void init() {
ResetGraph(0);
InitPad(NULL, 0, NULL, 0);
CdInit();
SetVideoMode(MODE_NTSC);
FntLoad(960, 256);
FntOpen(16, 16, 256, 224, 0, 512);
}
void load_texture_from_sector(int start_sector) {
// Чтение текстуры из указанного сектора CD-ROM в RAM через DMA
CdRead(start_sector, (uint32_t*)tex_buffer, TEX_SECTORS);
CdReadSync(0); // Ожидание завершения DMA
}
void draw_sprite(Sprite* sprite) {
// Настройка полупрозрачности для эффекта яркости
float alpha = (float)sprite->brightness / 128.0f;
DR_MODE dr_mode;
SetDrawMode(&dr_mode, 0, 0, GetTPage(0, 0, 0, 0), 0);
SetSprt16(&sprite->img_rect);
SetSemiTrans(&sprite->img_rect, 1);
setRGB0(&sprite->img_rect, alpha * 255, alpha * 255, alpha * 255);
DrawPrim(&sprite->img_rect);
}
int main() {
init();
// Параметры загрузки (пример: текстура начинается с сектора 1000)
int texture_start_sector = 1000; // Замените на реальный сектор!
load_texture_from_sector(texture_start_sector);
// Настройка спрайта
Sprite sprite = {
.img_rect = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0},
.brightness = 0,
.fade_speed = 1
};
// Загрузка текстуры в VRAM (адрес в RAM -> VRAM)
RECT vram_rect = {0, 0, TEX_WIDTH, TEX_HEIGHT}; // Куда загружать в VRAM
LoadImage(&vram_rect, (uint32_t*)tex_buffer);
// Главный цикл
while (1) {
FntPrint("Sector Load Demo\nBrightness: %d\nSector: %d",
sprite.brightness, texture_start_sector);
// Плавное изменение яркости
sprite.brightness += sprite.fade_speed;
if (sprite.brightness >= 128 || sprite.brightness <= 0)
sprite.fade_speed *= -1;
// Отрисовка
draw_sprite(&sprite);
FntFlush(-1);
VSync(0);
}
return 0;
}
```
---
### **2. Ключевые изменения**
1. **Загрузка из сектора**:
- Вместо поиска файла (`CdSearchFile`) используется прямой доступ к сектору:
```c
CdRead(start_sector, (uint32_t*)tex_buffer, TEX_SECTORS);
```
- `start_sector` — начальный сектор текстуры на диске (например, `1000`).
2. **Размер текстуры**:
- Рассчитывается автоматически:
```c
#define TEX_SECTORS (TEX_SIZE / 2048) // 2048 байт = 1 сектор Mode 1
```
3. **Выравнивание в RAM**:
- Буфер `tex_buffer` выровнен по 64 байта для эффективного DMA:
```c
uint16_t tex_buffer[...] __attribute__((aligned(64)));
```
4. **Загрузка в VRAM**:
- `LoadImage` копирует данные из `tex_buffer` (RAM) в VRAM:
```c
LoadImage(&vram_rect, (uint32_t*)tex_buffer);
```
---
### **3. Как подготовить диск**
1. **Конвертируйте текстуру** в RAW 16-бит (например, через Photoshop или `ffmpeg`):
```bash
ffmpeg -i texture.png -f rawvideo -pix_fmt rgb565 texture.raw
```
2. **Запишите RAW-данные** на CD в нужный сектор (например, с помощью `mkpsxiso`):
```bash
mkpsxiso -y -o texture.cue -c "CDROM_DATA" texture.raw
```
- Узнайте начальный сектор через `cdrdao` или hex-редактор.
---
### **4. Важные нюансы**
- **Режим CD-ROM**: Данные должны быть записаны в **Mode 1** (2048 байт/сектор).
- **Размер текстуры**: Должен быть кратен размеру сектора (иначе чтение "сломает" данные).
- **Проверка сектора**: Убедитесь, что сектор не занят другими файлами.
---
### **5. Пример для сборки**
1. Скомпилируйте:
```bash
ccpsx MAIN.C -o CD_SECTOR_DEMO.EXE
```
2. Запишите на диск, указав в `SYSTEM.CNF`:
```
BOOT = cdrom:\CD_SECTOR_DEMO.EXE;1
```
---
### **6. Дополнительная оптимизация**
Для работы с **Mode 2 XA** (формат видео) измените:
```c
CdReadMode(CdMode_CD_XA); // Перед CdRead
```
и корректируйте размер сектора (2336 байт).
---
Этот код идеален для демо-сцен, где нужно напрямую читать графику с CD без файловой системы! 📀

View File

@@ -0,0 +1,171 @@
Вот готовая программа для **PlayStation 1**, которая загружает текстуру с CD-ROM, передаёт её в VRAM и плавно выводит на экран с анимацией яркости (от 0 до 128). Код использует **PsyQ SDK** и разбит на логические блоки.
---
### **1. Структура проекта**
```
texture_demo/
├── MAIN.C # Основной код
├── TEXTURE.BIN # RAW-текстура 256x256 (16-бит RGB)
└── SYSTEM.CNF # Конфигурационный файл
```
---
### **2. Код программы (`MAIN.C`)**
```c
#include <sys/types.h>
#include <libetc.h>
#include <libgte.h>
#include <libgpu.h>
#include <libcd.h>
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
// Буфер для текстуры (256x256, 16-бит)
#define TEX_SIZE (256 * 256 * 2) // 131072 байт
uint16_t tex_buffer[256 * 256] __attribute__((aligned(64))); // Выравнивание для DMA
// Параметры спрайта
typedef struct {
RECT img_rect; // Размеры текстуры
TIM_IMAGE tim; // Данные TIM-файла
int brightness; // Текущая яркость (0-128)
int fade_speed; // Скорость изменения яркости
} Sprite;
void init() {
// Инициализация системных библиотек
ResetGraph(0);
InitPad(NULL, 0, NULL, 0);
CdInit();
// Настройка видеорежима (NTSC)
SetVideoMode(MODE_NTSC);
FntLoad(960, 256);
FntOpen(16, 16, 256, 224, 0, 512);
}
void load_texture(const char* filename) {
CdlFILE file;
uint8_t sector_buffer[2048]; // Буфер для сектора CD
// Поиск файла на диске
if (CdSearchFile(&file, filename) == 0) {
FntPrint("Error: File not found!\n");
return;
}
// Чтение текстуры в буфер через DMA
CdRead(file.pos, (uint32_t*)tex_buffer, TEX_SIZE / 2048);
CdReadSync(0); // Ожидание завершения
}
void draw_sprite(Sprite* sprite) {
// Установка яркости (0-128 → 0.0-1.0)
float alpha = (float)sprite->brightness / 128.0f;
// Рисуем полноэкранный спрайт с альфа-смешением
DR_MODE dr_mode;
SetDrawMode(&dr_mode, 0, 0, GetTPage(0, 0, sprite->tim.prect->x, sprite->tim.prect->y), 0);
SetSprt16(&sprite->img_rect); // 16-битный спрайт
// Применяем яркость через полупрозрачность
SetSemiTrans(&sprite->img_rect, 1);
setRGB0(&sprite->img_rect, alpha * 255, alpha * 255, alpha * 255);
// Вывод спрайта
DrawPrim(&sprite->img_rect);
}
int main() {
init(); // Инициализация
// Загрузка текстуры
load_texture("\\TEXTURE.BIN"); // Путь на CD
// Настройка спрайта
Sprite sprite = {
.img_rect = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT},
.brightness = 0,
.fade_speed = 1
};
// Загрузка текстуры в VRAM
sprite.tim.mode = 0;
sprite.tim.prect = (RECT*)tex_buffer;
LoadImage(&sprite.tim.prect[0], &sprite.tim.pdata[0]);
// Главный цикл
while (1) {
FntPrint("Fade Demo: Brightness = %d\n", sprite.brightness);
// Плавное увеличение яркости
sprite.brightness += sprite.fade_speed;
if (sprite.brightness >= 128 || sprite.brightness <= 0)
sprite.fade_speed *= -1; // Меняем направление
// Отрисовка
draw_sprite(&sprite);
FntFlush(-1);
// Синхронизация с VBlank
VSync(0);
}
return 0;
}
```
---
### **3. Как это работает?**
1. **Инициализация**:
- Настраивается видеорежим (NTSC 320x240).
- Инициализируются CD-ROM и геймпад.
2. **Загрузка текстуры**:
- Файл `TEXTURE.BIN` (RAW 256x256, 16-бит) читается с диска через DMA.
- Данные копируются в буфер `tex_buffer`.
3. **Передача в VRAM**:
- `LoadImage()` загружает текстуру в VRAM (адрес автоматически выбирается GPU).
4. **Плавное затемнение**:
- В главном цикле меняется параметр `brightness` (0 → 128 → 0).
- Яркость применяется через **полупрозрачность** (`SetSemiTrans`).
5. **Вывод**:
- Спрайт рисуется на весь экран с текущей яркостью.
---
### **4. Требования к текстуре**
- Формат: **RAW 16-бит RGB** (без заголовков).
- Размер: **256x256** (можно изменить в коде).
- Размещение: корень CD (`TEXTURE.BIN`).
---
### **5. Сборка**
1. Скомпилируйте через **PsyQ SDK**:
```bash
ccpsx MAIN.C -o TEXTURE_DEMO.EXE
```
2. Запишите на диск вместе с `TEXTURE.BIN` и `SYSTEM.CNF`.
---
### **6. Демонстрация эффекта**
- Текстура плавно "проявляется" (от чёрного к нормальной яркости) и затем исчезает.
- Скорость регулируется `fade_speed`.
---
### **7. Оптимизации**
- Для плавности используется **VSync(0)**.
- DMA ускоряет загрузку текстуры.
- Полупрозрачность GPU имитирует изменение яркости.
Этот код можно расширить для загрузки анимаций или сложных эффектов! 🎮

View File

@@ -0,0 +1,369 @@
Вот доработанная версия с **общими переменными между основной программой и модулями**. Мы создадим единую структуру `GameState`, которая будет передаваться в каждый модуль.
---
### **1. Общий заголовок (SHARED.H)**
```c
// Структура для общих переменных игры
typedef struct {
int lives; // Количество жизней
int level; // Текущий уровень
bool is_paused; // Состояние паузы
uint32_t score; // Счёт игрока
} GameState;
// Заголовок модуля
typedef void (*ModuleEntry)(GameState*); // Тип функции-входа
typedef struct {
uint32_t magic; // 0x55AA1234
ModuleEntry entry; // Функция входа
uint32_t size; // Размер модуля
} ModuleHeader;
```
---
### **2. Основная программа (MAIN.C)**
```c
#include <libetc.h>
#include <libcd.h>
#include "SHARED.H"
#define MODULE_ADDR 0x80030000 // Адрес загрузки модулей
GameState game_state = {
.lives = 3,
.level = 1,
.is_paused = false,
.score = 0
};
void load_and_run_module(int sector, int size) {
CdRead(sector, (uint32_t*)MODULE_ADDR, size);
CdReadSync(0);
ModuleHeader *header = (ModuleHeader*)MODULE_ADDR;
if (header->magic == 0x55AA1234) {
header->entry(&game_state); // Передаём указатель на game_state
}
}
int main() {
ResetGraph(0);
CdInit();
FntLoad(960, 256);
FntOpen(16, 16, 256, 224, 0, 512);
while (1) {
// Обновляем общие переменные
game_state.score += 10;
FntPrint("Main: Lives=%d, Score=%d\n", game_state.lives, game_state.score);
// Загружаем и выполняем модули
load_and_run_module(MODULE_MDEC_SECTOR, MODULE_MDEC_SIZE);
load_and_run_module(MODULE_GPU_SECTOR, MODULE_GPU_SIZE);
VSync(0);
}
}
```
---
### **3. Модуль для MDEC (MODULE_MDEC.C)**
```c
#include <libmdec.h>
#include "SHARED.H"
void module_entry(GameState *state) {
// Читаем и изменяем общие переменные
if (state->lives > 0) {
state->lives--; // Уменьшаем жизни
FntPrint("MDEC: Lives left=%d\n", state->lives);
}
// Декодируем видео только если игра не на паузе
if (!state->is_paused) {
uint8_t data[1024];
uint16_t buffer[256*256];
MDEC_DecodeFrame(data, buffer, MDEC_MODE_RGB24);
}
}
ModuleHeader __header__ __attribute__((section(".header"))) = {
.magic = 0x55AA1234,
.entry = module_entry,
.size = 0
};
```
---
### **4. Модуль для GPU (MODULE_GPU.C)**
```c
#include <libgpu.h>
#include "SHARED.H"
void module_entry(GameState *state) {
// Проверяем глобальный статус
if (state->is_paused) {
FntPrint("GPU: Game is paused!\n");
return;
}
// Рисуем HUD с общими переменными
char hud_text[32];
sprintf(hud_text, "LIVES: %d SCORE: %d", state->lives, state->score);
FntPrint(hud_text);
// Пример работы с GPU
RECT rect = {10, 10, 100, 100};
uint16_t color = state->lives > 1 ? 0xFF00 : 0xFF0000; // Красный если 1 жизнь
DrawPrim(&rect);
}
ModuleHeader __header__ __attribute__((section(".header"))) = {
.magic = 0x55AA1234,
.entry = module_entry,
.size = 0
};
```
---
### **5. Сборка**
```bash
# Компиляция модулей с их библиотеками
ccpsx MODULE_MDEC.C -o MODULE_MDEC.BIN -lpsxmdec
ccpsx MODULE_GPU.C -o MODULE_GPU.BIN -lpsxgpu
# Основная программа (без лишних библиотек)
ccpsx MAIN.C -o MAIN.EXE
```
---
### **6. Механика общих переменных**
1. **Основная программа**:
- Создаёт глобальную структуру `GameState`.
- Передаёт **указатель** на неё в модули через `header->entry(&game_state)`.
2. **Модули**:
- Читают и изменяют `GameState` через полученный указатель.
- Могут проверять:
```c
if (state->is_paused) { ... }
```
- Могут изменять:
```c
state->lives--;
```
3. **Синхронизация**:
- Все изменения мгновенно видны в основном модуле и других модулях.
---
### **7. Пример работы**
1. Основная программа:
- Устанавливает `lives = 3`, `score = 0`.
2. Модуль MDEC:
- Уменьшает `lives` до 2, если игрок ошибся.
3. Модуль GPU:
- Отображает текущие `lives` и `score` на экране.
4. Основная программа:
- Видит изменения и может реагировать (например, завершить игру при `lives == 0`).
---
### **8. Важные нюансы**
- **Безопасность**: Модули могут **перезаписать любые данные** в `GameState`. Добавьте проверки:
```c
if (state->lives < 0) state->lives = 0; // Защита от отрицательных значений
```
- **Производительность**: Частый доступ к общим переменным может замедлить работу. Оптимизируйте:
```c
int local_lives = state->lives; // Кэширование в локальную переменную
```
- **Конфликты**: Если модули работают с одними данными, добавьте **флаги блокировки**.
---
Теперь модули могут **полноценно взаимодействовать** с основной программой, проверяя и изменяя глобальное состояние игры! 🎮
-------
Да, вы правильно поняли! В этой архитектуре **основная программа** и **модули** компилируются по-разному:
---
### **1. Основная программа (MAIN.EXE)**
- **Не требует** явного указания библиотек PsyQ (если не использует их напрямую).
- Её задача — только **загружать модули** и **управлять общим состоянием** (`GameState`).
- Пример компиляции:
```bash
ccpsx MAIN.C -o MAIN.EXE # Без -lpsxetc, -lpsxgpu и т.д.
```
---
### **2. Дополнительные модули (MODULE_*.BIN)**
- **Должны включать** нужные библиотеки PsyQ, так как:
- Каждый модуль — **самодостаточный** исполняемый код.
- PsyQ не загружается глобально для модулей (они работают в изолированном контексте).
- Пример компиляции:
```bash
# Модуль для GPU
ccpsx MODULE_GPU.C -o MODULE_GPU.BIN -lpsxgpu -lpsxgte
# Модуль для MDEC
ccpsx MODULE_MDEC.C -o MODULE_MDEC.BIN -lpsxmdec
```
---
### **3. Почему такая разница?**
| Компонент | Библиотеки PsyQ | Причина |
|--------------------|----------------|-------------------------------------------------------------------------|
| **Основная программа** | Не нужны | Только загружает модули и передаёт `GameState`. Не использует GPU/MDEC. |
| **Модули** | Обязательны | Каждый модуль — независимый код, требующий свои библиотеки. |
---
### **4. Как это работает в памяти?**
1. Основная программа:
- Занимает **нижнюю часть RAM** (например, `0x80010000`).
- Не знает о существовании PsyQ-функций.
2. Модули:
- Загружаются в **фиксированный адрес** (`0x80030000`).
- Содержат **свои копии библиотек** (например, `libgpu`).
- Получают доступ к `GameState` через указатель.
---
### **5. Пример: вызов функций**
- В модуле `MODULE_GPU.C`:
```c
#include <libgpu.h> // Библиотека внутри модуля
void module_entry(GameState *state) {
// Использует свою копию LoadImage
RECT rect = {0, 0, 256, 256};
LoadImage(&rect, (uint32_t*)texture); // Функция из libgpu
}
```
- В основной программе (`MAIN.C`):
```c
// Нет #include <libgpu.h>!
void load_module() {
// Только загрузка бинарника
CdRead(sector, (uint32_t*)0x80030000, size);
}
```
---
### **6. Ограничения**
- **Дублирование кода**: Каждый модуль содержит свои библиотеки → увеличивает размер.
- **Нет глобального состояния**: Если `libgpu` хранит внутренние переменные, они будут разными для каждого модуля.
- **Только последовательное выполнение**: Модули не могут работать одновременно (перезаписывают один регион памяти).
---
### **7. Оптимизация**
Если модули используют **одни и те же библиотеки**, можно:
1. Вынести общие библиотеки в **отдельный загружаемый модуль**.
2. Передавать **указатели на функции** (как в предыдущих примерах).
---
### **8. Итог**
- **Основная программа** — минималистична, без зависимостей.
- **Модули** — самодостаточные бинарники со своими библиотеками.
- **Общение** через общую структуру `GameState`.
Это архитектурно напоминает **плагины в современных движках**, где каждый плагин собирается со своими зависимостями.
------------------
Да, вы абсолютно правы! Моя ошибка — **основная программа действительно использует `libcd.h` (и другие библиотеки PsyQ для базовых операций)**, но **ключевое различие** в следующем:
---
### **1. Уточнение архитектуры**
| Компонент | Библиотеки PsyQ | Почему? |
|--------------------|------------------------------------|-------------------------------------------------------------------------|
| **Основная программа** | `libcd.h`, `libetc.h`, `libgte.h` | Только для **загрузки модулей**, управления памятью и отладки (FntPrint). Не использует GPU/MDEC/SPU. |
| **Модули** | Свои специфичные библиотеки (`libgpu`, `libmdec`) | Каждый модуль — самостоятельный код, требующий **все свои зависимости**. |
---
### **2. Исправленный пример компиляции**
#### **Основная программа (MAIN.C)**
```bash
# Нужны только базовые библиотеки для загрузки модулей
ccpsx MAIN.C -o MAIN.EXE -lpsxcd -lpsxetc -lpsxgte
```
#### **Модули**
```bash
# GPU-модуль (со своим libgpu)
ccpsx MODULE_GPU.C -o MODULE_GPU.BIN -lpsxgpu -lpsxgte
# MDEC-модуль (со своим libmdec)
ccpsx MODULE_MDEC.C -o MODULE_MDEC.BIN -lpsxmdec
```
---
### **3. Почему основная программа не включает `libgpu`/`libmdec`?**
- Она **не вызывает** функции вроде `LoadImage()` или `MDEC_Decode()`.
- Её задача — лишь:
- Чтение секторов с диска (`libcd`).
- Вывод текста (`libetc`).
- Передача управления модулям.
---
### **4. Аналогия с операционной системой**
- **Основная программа** — как **ядро ОС**, которое:
- Умеет загружать драйверы (модули).
- Не содержит код для работы с графикой/звуком.
- **Модули** — как **драйверы**, которые:
- Содержат весь специфичный код (например, для GPU).
- Получают доступ к «ядру» через общие структуры (`GameState`).
---
### **5. Полный список зависимостей**
#### Для **MAIN.C**:
```c
#include <libcd.h> // CdRead(), CdInit()
#include <libetc.h> // FntPrint(), VSync()
#include <libgte.h> // Базовая математика (если используется)
```
#### Для **MODULE_GPU.C**:
```c
#include <libgpu.h> // LoadImage(), DrawPrim()
#include <libetc.h> // FntPrint() (если нужно)
```
---
### **6. Итог**
- **Основная программа** использует **минимальный набор PsyQ** (`libcd`, `libetc`).
- **Модули** включают **все необходимые им библиотеки** (`libgpu`, `libmdec` и т.д.).
- **Общение** между ними происходит через **общую структуру** `GameState`.
Спасибо за замечание! Теперь архитектура стала ещё понятнее 😊.

View File

@@ -0,0 +1,123 @@
Вот готовая программа для **PlayStation 1**, которая использует **MIPS-ассемблерные вставки** для сложения двух чисел через указатели. Результат возвращается в переменную основной программы.
---
### **1. Полный код (MAIN.C)**
```c
#include <stdio.h>
#include <libetc.h>
// Объявляем ассемблерную функцию
int add_numbers_asm(int *a, int *b, int *result);
int main() {
int num1 = 5;
int num2 = 7;
int result = 0;
// Вызываем ассемблерную функцию с указателями
add_numbers_asm(&num1, &num2, &result);
// Выводим результат
FntPrint("Result: %d + %d = %d\n", num1, num2, result);
FntFlush(-1);
while (1) {
VSync(0);
}
return 0;
}
// Реализация на MIPS-ассемблере
int add_numbers_asm(int *a, int *b, int *result) {
int ret;
asm volatile (
"lw $t0, 0(%1) \n" // Загружаем *a в $t0
"lw $t1, 0(%2) \n" // Загружаем *b в $t1
"add $t2, $t0, $t1 \n" // Складываем: $t2 = $t0 + $t1
"sw $t2, 0(%3) \n" // Сохраняем результат по адресу *result
"move %0, $t2 \n" // Возвращаем результат через регистр
: "=r" (ret) // Выходные операнды
: "r" (a), "r" (b), "r" (result) // Входные операнды
: "$t0", "$t1", "$t2" // Разрушаемые регистры
);
return ret;
}
```
---
### **2. Как это работает?**
1. **Передача указателей в ассемблер**
Функция `add_numbers_asm` принимает три указателя:
- `a` — адрес первого числа,
- `b` — адрес второго числа,
- `result` — адрес для сохранения результата.
2. **Ассемблерная вставка** (`asm volatile`):
- `lw` — загружает значения из памяти по указателям.
- `add` — складывает числа.
- `sw` — сохраняет результат по адресу `result`.
- `move` — возвращает значение через регистр.
3. **Доступ к результату**
Результат будет:
- В переменной `result` (через указатель).
- В возвращаемом значении функции (если нужно).
---
### **3. Компиляция и запуск**
1. Скомпилируйте через **PsyQ SDK**:
```bash
ccpsx MAIN.C -o ADD_DEMO.EXE
```
2. Запишите на диск и запустите на эмуляторе или консоли.
На экране появится:
```
Result: 5 + 7 = 12
```
---
### **4. Важные нюансы**
- **Регистры MIPS**:
- `$t0``$t9` — временные регистры (можно использовать без сохранения).
- `$s0``$s7` — должны сохраняться (если используются).
- **Разрушаемые регистры**:
Указаны в конце `asm volatile` (чтобы компилятор не использовал их для оптимизации).
- **Опасность**:
Если передать **неверные указатели**, PS1 уйдёт в **краш** (нет защиты памяти).
---
### **5. Пример с глобальными переменными**
Если нужно работать с глобальными переменными напрямую:
```c
int global_num1 = 10;
int global_num2 = 20;
void add_globals_asm() {
asm volatile (
"la $t0, global_num1 \n" // Загружаем адрес global_num1
"lw $t1, 0($t0) \n" // Читаем значение
"la $t0, global_num2 \n"
"lw $t2, 0($t0) \n"
"add $t1, $t1, $t2 \n"
"sw $t1, 0($t0) \n" // Сохраняем сумму в global_num2
);
}
```
---
### **6. Итог**
- **Ассемблерные вставки** дают полный контроль над железом PS1.
- **Указатели** передаются через регистры (`$a0``$a3` в MIPS).
- **Результат** можно получить через память или возвращаемое значение.
Этот подход используется в оптимизированных частях игр (например, в рендеринге или физике).

View File

@@ -0,0 +1,183 @@
Вот готовая программа для **PlayStation 1**, которая загружает текстуру, плавно проявляет её, ждёт нажатия кнопки (✅ **CROSS** или **CIRCLE**), а затем плавно исчезает. Всё работает с ручным управлением адресами RAM и без использования полупрозрачности.
---
### **Код (`MAIN.C`)**
```c
#include <sys/types.h>
#include <libetc.h>
#include <libgte.h>
#include <libgpu.h>
#include <libcd.h>
// Константы
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define TEX_WIDTH 256
#define TEX_HEIGHT 256
#define RAW_SECTOR_SIZE 2048
#define TEX_SECTORS ((TEX_WIDTH * TEX_HEIGHT * 2) / RAW_SECTOR_SIZE)
// Адреса в RAM
#define RAW_DATA_ADDR 0x80010000 // Сырые данные с CD
#define UNPACKED_ADDR 0x80030000 // Распакованная текстура
// Состояния анимации
typedef enum {
FADE_IN, // Появление текстуры
WAIT_FOR_BUTTON, // Ожидание нажатия кнопки
FADE_OUT // Исчезновение
} State;
// Структура спрайта
typedef struct {
RECT img_rect;
uint16_t *texture;
int brightness; // 0-128 (0 = чёрный, 128 = оригинал)
State state;
} Sprite;
// Заглушка распаковки (замените на реальный алгоритм)
void unpack_texture(uint8_t *src, uint16_t *dst, int width, int height) {
for (int i = 0; i < width * height; i++) {
dst[i] = ((uint16_t*)src)[i]; // Простое копирование (данные уже в RGB565)
}
}
void init() {
ResetGraph(0);
InitPad(NULL, 0, NULL, 0);
CdInit();
SetVideoMode(MODE_NTSC);
FntLoad(960, 256);
FntOpen(16, 16, 256, 224, 0, 512);
}
void load_and_unpack_texture(int start_sector) {
// Чтение данных с CD в RAW_DATA_ADDR через DMA
CdRead(start_sector, (uint32_t*)RAW_DATA_ADDR, TEX_SECTORS);
CdReadSync(0);
// Распаковка в UNPACKED_ADDR
unpack_texture((uint8_t*)RAW_DATA_ADDR, (uint16_t*)UNPACKED_ADDR, TEX_WIDTH, TEX_HEIGHT);
}
void draw_sprite(Sprite *sprite) {
// Применение яркости через RGB
int r, g, b;
if (sprite->brightness <= 0) {
r = g = b = 0; // Полная темнота
} else {
float mult = (float)sprite->brightness / 128.0f;
r = 255 * mult;
g = 255 * mult;
b = 255 * mult;
}
// Настройка и отрисовка спрайта
DR_MODE dr_mode;
SetDrawMode(&dr_mode, 0, 0, GetTPage(0, 0, 0, 0), 0);
SetSprt16(&sprite->img_rect);
setRGB0(&sprite->img_rect, r, g, b);
DrawPrim(&sprite->img_rect);
}
int main() {
init();
// Загрузка текстуры из сектора 1000 (пример)
int texture_start_sector = 1000;
load_and_unpack_texture(texture_start_sector);
// Инициализация спрайта
Sprite sprite = {
.img_rect = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0},
.texture = (uint16_t*)UNPACKED_ADDR,
.brightness = 0,
.state = FADE_IN
};
// Загрузка текстуры в VRAM
RECT vram_rect = {0, 0, TEX_WIDTH, TEX_HEIGHT};
LoadImage(&vram_rect, (uint32_t*)UNPACKED_ADDR);
// Главный цикл
while (1) {
// Обработка состояния
switch (sprite.state) {
case FADE_IN:
sprite.brightness += 2;
if (sprite.brightness >= 128) {
sprite.brightness = 128;
sprite.state = WAIT_FOR_BUTTON;
}
break;
case WAIT_FOR_BUTTON: {
// Проверка нажатия CROSS (✅) или CIRCLE (🟠)
u_long pad = PadRead(0);
if (pad & PAD_CROSS || pad & PAD_CIRCLE) {
sprite.state = FADE_OUT;
}
break;
}
case FADE_OUT:
sprite.brightness -= 2;
if (sprite.brightness <= 0) {
sprite.brightness = 0;
// Здесь можно перейти к следующей сцене
}
break;
}
// Отрисовка
draw_sprite(&sprite);
FntPrint("Press X or O to fade out\nBrightness: %d", sprite.brightness);
FntFlush(-1);
VSync(0);
}
return 0;
}
```
---
### **Как это работает?**
1. **FADE_IN**:
- Текстура плавно проявляется (яркость увеличивается от 0 до 128).
2. **WAIT_FOR_BUTTON**:
- Программа ждёт нажатия **CROSS** (✅) или **CIRCLE** (🟠) через `PadRead()`.
3. **FADE_OUT**:
- После нажатия текстура исчезает (яркость уменьшается до 0).
---
### **Ключевые особенности**
- **Ручное управление адресами RAM** (`0x80010000` и `0x80030000`).
- **Яркость через RGB**, а не полупрозрачность.
- **Проверка кнопок** через битовые маски:
- `PAD_CROSS` — кнопка ✅.
- `PAD_CIRCLE` — кнопка 🟠.
---
### **Сборка**
1. Скомпилируйте:
```bash
ccpsx MAIN.C -o FADE_DEMO.EXE
```
2. Запишите на диск, указав в `SYSTEM.CNF`:
```
BOOT = cdrom:\FADE_DEMO.EXE;1
```
---
### **Демонстрация**
1. Текстура плавно появляется.
2. Нажмите **X** или **O** на геймпаде.
3. Текстура исчезает в темноту.
Теперь вы можете интегрировать это в свою игру для cinematic-сцен! 🎮