Автор Тема: Хранение клона OAM таблицы в PRG-ROM NES  (Прочитано 1779 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« : 25 Декабрь 2022, 23:09:50 »
Здравствуйте. Разбираюсь в программировании Nes на си, пока все более менее понятно. Но один вопрос я сам не смог решил, хотя гуглил кране активно.
В документации и статьях в интернете утверждается, что есть возможность хранить копию  OAM таблицы в PRG-ROM для загрузки ее в OAM PPU, а не только в RAM.
Есть я выделяю память под таблицу в $0200-$0600 или в $6000-7F00, то все работает отлично.
 А если пытаюсь хранить таблицу на $8000-$BF00, то на экране артефакты и программа зависает.
Если создаю таблицу на $С000-$FF00 в свободной части, то просто черный экран без артефактов на экране.
И я понимаю, что это не самое оптимальное решение, но мне нужно нормальное осознание архитектуры процессора, поэтому стараюсь освоить все его основные фишки.
// Так я записываю старшие разряды адреса таблицы (именно так сделал для наглядности того, что адрес точно верный передается)
// SPRITES - массив таблицы
#define OAM_DMA *((unsigned char*)0x4014)
a = (unsigned  int) SPRITES;
OAM_DMA     = a / 0x100;
Код программы у меня храните в $С000 блоке, $8000-$BFFF - не использую.
Возможно я как-то нарушаю разметку кода? Но я самые разные варианты перепробовал, результатов ноль.
Надеюсь, что вы мне поможете с этой проблемой, ибо дальше осваивать процессор не могу, пока не пойму этот момент. Спасибо.
« Последнее редактирование: 25 Декабрь 2022, 23:34:50 от Howard Phillips »

Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5182
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM
« Ответ #1 : 25 Декабрь 2022, 23:26:16 »
При программировании на C под NES указываются сегменты, вы не показываете как у вас это сделано, лучше покажите весь проект и скажите, что за компилятор.

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #2 : 25 Декабрь 2022, 23:46:11 »
Компилятор я использую cc65.
Архив прикрепляю (это рабочая версия с таблицей в ОЗУ). В nes.cfg описана разметка (комментарии я делал для себя, пока разбирался с архитектурой). Структуру разметки там видно, скорее всего я что-то не так размечаю.
Надеюсь, что вы подскажите как правильно настроить разметку, чтоб держать OAM в PRG-ROM.

Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5182
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #3 : 26 Декабрь 2022, 01:43:27 »
Howard Phillips, PRG ROM нельзя менять, поэтому нельзя использовать разметку по аналогии с OAM в RAM (вы изменяете массив SPRITES). Хранить в PRG ROM можно только постоянную OAM, подойдёт разве что для статичной картинки. По идее нужно просто определить массив const unsigned char как у вас PALETTE, но с выравниванием до 256 байт. Не знаю как сделать в cc65 для отдельного массива на C. Наверно, можно добавить в nes.cfg к SEGMENTS:
RODATA_OAM: load = PRG, type = ro, define = yes, align = $100;В DEFINE.c:
#pragma rodata-name(push, "RODATA_OAM") // Сегмент выравненный до 256 байт
const unsigned char SPRITES[256] = {0}; // Заполнить заранее данными OAM
#pragma rodata-name(pop) // Вернуть сегмент
Не проверял, не знаю как собирать ром.
Кстати, так не делают: #include "DEFINE.c". #include для .h файлов, но тогда там лучше не определять переменные, только объявлять.
UPD: Заменил rodataseg на rodata-name, смотрел старые доки, а новые здесь: https://www.cc65.org/snapshot-doc/.
UPD2: Проверил, работает. В nes.cfg убрал сегмент OAM, потому что его не использовал и была ошибка. В main.c убрал запись в SPRITES, иначе ошибка компиляции. В define.c в SPRITES указал байты из одного из состояний main.nes. Для компиляции я закинул в папку с проектом файлы от cc65: ca65.exe, cc65.exe, ld65.exe, longbranch.mac, nes.lib, zeropage.inc. Запускал команды по очереди:
cc65.exe -O -t nes main.c
ca65.exe main.s
ca65.exe reset.s
ld65.exe -o mainPRGOAM.nes -t nes main.o reset.o nes.lib
Знаю, что нужно писать Makefile :) Ох, там ещё есть cl65 для упрощения.
« Последнее редактирование: 26 Декабрь 2022, 14:47:16 от Sharpnull »

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #4 : 26 Декабрь 2022, 15:46:30 »
Sharpnull, точно, я как-то не подумал, что в названии PRG-ROM слово ROM не просто так присутствует, причем еще и двигать пытался спрайт их таблицы. Просто фейспалм. Как-то я еще значит не привык к особенностям NES, 5 дней значит мало для формирования привычки :)
Спасибо, что заморочились с  подробным ответом. Все заработало сразу, когда последовал вашим советам. Работает даже без const, компилятор не ругается.
Структура проекта там вообще спорная у меня, ибо я за основу брал чужой скелет, а в него уже лепил свои эксперименты. Отсюда #include "DEFINE.c" и прочие приколы.
Разработкой я занимаю я в VS Code. Я прописал универсальный батник, которому я хоткеем передаю текущий .с файл на компиляцию и автоматически запускается эмулятор после сборки. Очень удобно. И студия приятная, хороший редактор.
И почти в эту же тему есть еще небольшой вопрос.
Вот я описываю сегмент кода PRG-ROM:
PRG: start = $c000, size = $3ffa, file = %O ,fill = yes, define = yes;
Тут я храню PRG во второй половине доступной ROM. А если пытаюсь использовать всю память:
PRG: start = $8000, size = $7f00, file = %O ,fill = yes, define = yes;
То серый экран и программа висит, при этом больше ничего не менял, только адреса сегмента.
Я предполагаю, что это нужно еще прописывать в сегменте HEADER. У меня он такой:
.segment "HEADER"
    .byte $4e,$45,$53,$1a
.byte 01
.byte 01
.byte 00
.byte 00
.res 8,0
Но документацию на параметры HEADER я почему-то не смог найти. Как мне использовать всю доступную ROM в проекте?
Пробовал  разбивать на два сегмента PRG1 и PRG2, но тогда компилятор ругается на переопределение.


Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5182
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #5 : 26 Декабрь 2022, 16:15:58 »
Но документацию на параметры HEADER я почему-то не смог найти.
У .nes ромов iNES 1.0 или NES 2.0 заголовок, вам нужно указать размер PRG ROM как 32КиБ. Можете в эмуляторах как Mesen открыть редактор заголовка и увидеть результат в HEX (байтах). Формат: https://www.nesdev.org/wiki/INES. Нужно заменить на:
.segment "HEADER"
    .byte $4e,$45,$53,$1a
.byte 02
...
UPD: Ещё вы указали PRG: start = $8000, size = $7f00, а нужно size = $7ffa, размер не совпадёт.
« Последнее редактирование: 26 Декабрь 2022, 16:47:19 от Sharpnull »

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #6 : 26 Декабрь 2022, 17:17:42 »
Sharpnull,  спасибо, все заработало.
Сколько тут все-таки тонкостей. Страшно представить разработку большой игры для NES, разработчикам прошлого большой респект.

Оффлайн nonamezerox

  • Пользователь
  • Сообщений: 322
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #7 : 27 Декабрь 2022, 11:57:43 »
Компилятор я использую cc65.

Переходите на шланг+LLVM-MOS, они сделали супергипервысокоэффективный компилятор отменяющий необходимость ассемблерных вставок.

Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5182
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #8 : 27 Декабрь 2022, 16:34:07 »
nonamezerox, забавно, но на C не получится сделать синхронизацию с точностью до тактов, которая нужна для эффектов, смены банков графики вне vblank и т. д. Аsm всё равно придётся использовать для чего-то интересного.
UPD: Есть вариант с языками между asm и C как https://github.com/KarolS/millfork и https://github.com/wiz-lang/wiz (пример https://8bitworkshop.com/v3.10.0/?platform=nes&file=hello.wiz).
« Последнее редактирование: 27 Декабрь 2022, 16:47:44 от Sharpnull »

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #9 : 27 Декабрь 2022, 17:50:20 »
nonamezerox, спасибо за наводку.
Посмотрел презентацию от разработчика, звучит грандиозно. Особенно забавно, если там реально кодогенерация с С++11 будет действительно на уровне рукопашного ассемблера.
Но пока переходить не буду, шас цель в оптимизации не стоит, пока я осваиваю архитектуру. А LLVM-MOS про уход на более высокий уровень абстракции. Если начну делать свой большой проект, то мб и перейду.

Sharpnull,  разработчики LLVM-MOS как раз утверждают, что код генерируется с точностью до такта и приводят демку, где необходим точный расчет тактов. Но я не вникал пока, может и врут. Но даже если там кодогененератор будет работать на уровне сс65, то использование  оправдано, ибо современный С++ сильно упрощает разработку. Хотя фишками С++11 и старше я особо так и не начал пользоваться.

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #10 : 24 Февраль 2023, 11:31:04 »
Здравствуйте. Я продолжаю делать свою игру под NES.
У меня есть функция опроса кнопок, она написана на асме и работает нормально. Но я решил переписать ее на Си. Вышло вот так:
void Get_Input2 () {
// Сторобируем геймпад
// Адрес геймпада - 0x4016
JOYPAD1 = 1;
JOYPAD1 = 0;
    temp = 0;
// Считываем значения всех кнопок
   
    temp |= JOYPAD1 <<  7; // Чтение А
    temp |= JOYPAD1 <<  6; // Чтение В
    temp |= JOYPAD1 <<  5; // Чтение Select
    temp |= JOYPAD1 <<  4; // Чтение Start
    temp |= JOYPAD1 <<  3; // Чтение Вверх
    temp |= JOYPAD1 <<  2; // Чтение Вниз
    temp |= JOYPAD1 <<  1; // Чтение Влево
    temp |= JOYPAD1 <<  0; // Чтение Вправо

    // Сохраняем предыдущие нажатия кнопок
    joypad1old = joypad1;
    // Сохравняем новое нажатие
joypad1 = temp;
}
Старший бит temp - это А, следующий это В и тд.
JOYPAD1 - дефайн 0x4016
Все переменные unsigned char
Логика чтения  простейшая, но почему-то кнопки А и Б не считываются, а остальные считываются нормально. У меня вообще нет идей почему так. Явно какая-то глупая ошибка или я упускаю какой-то важный момент механики работы геймпадов.
Может слишком быстро я пытаюсь считать все кнопки? Что я делаю не так?
Компилятор все тот же сс65.
Спасибо.

Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5182
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #11 : 24 Февраль 2023, 12:56:11 »
У меня есть функция опроса кнопок, она написана на асме и работает нормально. Но я решил переписать ее на Си.
Зря, в этом нет смысла и медленнее, здесь быстрый код - https://www.nesdev.org/wiki/Controller_reading_code и ещё быстрее, если раскрыть цикл, там есть код с учётом подключения контроллера в Famicom's DA15 expansion port.
Ошибка в том, что нужно считывать только 0-й бит (младший), а у вас происходит чтение вместе с open bus - 0x40 (https://www.nesdev.org/wiki/Controller_reading#Unconnected_data_lines_and_open_bus), поэтому в конце у вас: 0x40 | (0x40<<1).
UPD: Я боюсь представить как будет выглядеть ASM после JOYPAD1 <<  7, лучше сдвигать по одному биту, где возможно. Такой код должен быть нормальным (насколько возможно, без Famicom's DA15 expansion port):
void Get_Input2 () {
    // Сторобируем геймпад
    // Адрес геймпада - 0x4016
    JOYPAD1 = 1;
    JOYPAD1 = 0;

    temp = JOYPAD1;      // A
    temp <<= 1;
    temp |= JOYPAD1 & 1; // B
    temp <<= 1;
    temp |= JOYPAD1 & 1; // Select
    temp <<= 1;
    temp |= JOYPAD1 & 1; // Start
    temp <<= 1;
    temp |= JOYPAD1 & 1; // Up
    temp <<= 1;
    temp |= JOYPAD1 & 1; // Down
    temp <<= 1;
    temp |= JOYPAD1 & 1; // Left
    temp <<= 1;
    temp |= JOYPAD1 & 1; // Right

    // Сохраняем предыдущие нажатия кнопок
    joypad1old = joypad1;
    // Сохравняем новое нажатие
    joypad1 = temp;
}
« Последнее редактирование: 24 Февраль 2023, 14:28:53 от Sharpnull »

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #12 : 24 Февраль 2023, 14:46:16 »
Sharpnull,  точно. Спасибо за быстрый ответ. Я забыл, что в 0x4016 не только D0 хранится и почему-то думал, что сдвиг до 7 делается за одну команду. Ваш код заработал.
Я решил переписать на Си функцию чтения для большего единообразия кода и лучшей читаемости. Стараюсь минимизировать ассемблер. И у меня игра пошаговая, там нет борьбы за каждый такт. У меня основа движка написана уже, почти все написано на Си (Распаковка RLE только на асме теперь осталась), скорости пока хватает.
А по поводу оптимизации. Со сдвигами на один бит, получается вполне нормальный код. Вот начало функции после компиляции:
;
; JOYPAD1 = 1;
;
lda     #$01
sta     $4016
;
; JOYPAD1 = 0;
;
lda     #$00
sta     $4016
;
; temp = JOYPAD1;      // A
;
sta     _temp
;
; temp <<= 1;
;
asl     a
sta     _temp
Разве плохо? Руками было бы тоже самое. Что тут можно оптимизировать?

Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5182
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #13 : 24 Февраль 2023, 15:24:38 »
Разве плохо? Руками было бы тоже самое. Что тут можно оптимизировать?
Вы не показали самое интересное, что будет дальше с temp |= JOYPAD1 & 1;, каждый раз будет типа LDA $4016 | AND #$01 | ORA _temp | STA _temp | ASL _temp. Это при том, что вы забили на Famicom's DA15 expansion port, с ним будет заметно хуже. Если без Famicom, то можно записать:
    LDA $4016
    LSR A
    ROL _temp
    ... повторить ещё 7 раз
UPD: Не понял как у вас работают кнопки, он здесь бред сгенерировал:
; JOYPAD1 = 0;
;
lda     #$00
sta     $4016
;
; temp = JOYPAD1;      // A
;
sta     _temp
Нет чтения $4016, он типа оптимизировал до temp = JOYPAD1 = 0. Там нужно volatile, например, но не уверен:
#define JOYPAD1 (*(volatile unsigned char*)0x4016)Или в общем виде:
#define IO8(addr) (*(volatile unsigned char*)(addr))
#define JOYPAD1 IO8(0x5100)
Хотя у вас в проекте уже должно было определено правильно, иначе не работало бы ничего.
UPD2: Забыл сам же сделать оптимизацию 1-го вызова, должно быть так полностью:
  LDA #$01
  STA $4016
  LSR A ; A = 0
  STA $4016

  LDA $4016
  STA _temp
REPEAT 7 ; Макрос повторяющий N раз
  LDA $4016
  LSR A
  ROL _temp
ENDREPEAT
UPD3: Для чтения с расширением Famicom я давал ссылку (https://www.nesdev.org/wiki/Controller_reading_code). Можно записать с раскрытым циклом так:
  LDA #$01
  STA $4016
  LSR A ; A = 0
  STA $4016

REPEAT 8 ; Макрос повторяющий N раз
  LDA $4016
  AND #$03
  CMP #$01
  ROL _temp
ENDREPEAT
В игре Kage (Shadow of the Ninja в США, Blue Shadow в Европе) не догадались (как многие) и сделали с использованием лишней RAM (потом делают $90 | $92) и дольше выполняется:
  LDA #$01
  STA Ctrl1_4016
  LDA #$00
  STA Ctrl1_4016
Повторить 8 раз
  LDA Ctrl1_4016
  LSR A
  ROL $90
  LSR A
  ROL $92
КонецПовтора
« Последнее редактирование: 24 Февраль 2023, 16:15:58 от Sharpnull »

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #14 : 24 Февраль 2023, 17:51:28 »
Sharpnull, вот полный код функции сгенерированный:
.segment "CODE"

.proc _Get_Input2: near

.segment "CODE"
;
; JOYPAD1 = 1;
;
lda     #$01
sta     $4016
;
; JOYPAD1 = 0;
;
lda     #$00
sta     $4016
;
; temp = JOYPAD1;      // A
;
sta     _temp
;
; temp <<= 1;
;
asl     a
sta     _temp
;
; temp |= JOYPAD1 & 1; // B
;
lda     $4016
and     #$01
ora     _temp
sta     _temp
;
; temp <<= 1;
;
asl     a
sta     _temp
;
; temp |= JOYPAD1 & 1; // Select
;
lda     $4016
and     #$01
ora     _temp
sta     _temp
;
; temp <<= 1;
;
asl     a
sta     _temp
;
; temp |= JOYPAD1 & 1; // Start
;
lda     $4016
and     #$01
ora     _temp
sta     _temp
;
; temp <<= 1;
;
asl     a
sta     _temp
;
; temp |= JOYPAD1 & 1; // Up
;
lda     $4016
and     #$01
ora     _temp
sta     _temp
;
; temp <<= 1;
;
asl     a
sta     _temp
;
; temp |= JOYPAD1 & 1; // Down
;
lda     $4016
and     #$01
ora     _temp
sta     _temp
;
; temp <<= 1;
;
asl     a
sta     _temp
;
; temp |= JOYPAD1 & 1; // Left
;
lda     $4016
and     #$01
ora     _temp
sta     _temp
;
; temp <<= 1;
;
asl     a
sta     _temp
;
; temp |= JOYPAD1 & 1; // Right
;
lda     $4016
and     #$01
ora     _temp
sta     _temp
;
; joypad1old = joypad1;
;
lda     _joypad1
sta     _joypad1old
;
; joypad1 = temp;
;
lda     _temp
sta     _joypad1
;
; }
;
rts
Офтоп. А почему у меня не растягивается поле для написания сообщения? Хотя двустороння стрелка появляется. Это у меня проблема или глюк форума?

Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5182
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #15 : 24 Февраль 2023, 18:12:02 »
полный код функции сгенерированный:
Почти как думал, только asl a + sta _temp вместо asl _temp, что по скорости также, но занимает больше кода. Не читается кнопка A, неправильная генерация ASM, смотрите как определён JOYPAD1, я выше писал про правильное определение.
Можно написать короче, если в начале joypad1old = joypad1; и вместо temp использовать joypad1.
А почему у меня не растягивается поле для написания сообщения?
У меня работает в Firefox растягивание по вертикали.

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #16 : 24 Февраль 2023, 18:40:05 »
Sharpnull, хорошая мысль с выкидываем Temp. Вот сгенерированный код без volatile:
; void __near__ Get_Input2 (void)
; ---------------------------------------------------------------

.segment "CODE"

.proc _Get_Input2: near

.segment "CODE"

;
; JOYPAD1 = 1;
;
lda     #$01
sta     $4016
;
; JOYPAD1 = 0;
;
lda     #$00
sta     $4016
;
; joypad1old = joypad1;
;
lda     _joypad1
sta     _joypad1old
;
; joypad1 = JOYPAD1;      // A
;
lda     $4016
sta     _joypad1
;
; joypad1 <<= 1;
;
asl     a
sta     _joypad1
;
; joypad1 |= JOYPAD1 & 1; // B
;
lda     $4016
and     #$01
ora     _joypad1
sta     _joypad1
;
; joypad1 <<= 1;
;
asl     a
sta     _joypad1
;
; joypad1 |= JOYPAD1 & 1; // Select
;
lda     $4016
and     #$01
ora     _joypad1
sta     _joypad1
;
; joypad1 <<= 1;
;
asl     a
sta     _joypad1
;
; joypad1 |= JOYPAD1 & 1; // Start
;
lda     $4016
and     #$01
ora     _joypad1
sta     _joypad1
;
; joypad1 <<= 1;
;
asl     a
sta     _joypad1
;
; joypad1 |= JOYPAD1 & 1; // Up
;
lda     $4016
and     #$01
ora     _joypad1
sta     _joypad1
;
; joypad1 <<= 1;
;
asl     a
sta     _joypad1
;
; joypad1 |= JOYPAD1 & 1; // Down
;
lda     $4016
and     #$01
ora     _joypad1
sta     _joypad1
;
; joypad1 <<= 1;
;
asl     a
sta     _joypad1
;
; joypad1 |= JOYPAD1 & 1; // Left
;
lda     $4016
and     #$01
ora     _joypad1
sta     _joypad1
;
; joypad1 <<= 1;
;
asl     a
sta     _joypad1
;
; joypad1 |= JOYPAD1 & 1; // Right
;
lda     $4016
and     #$01
ora     _joypad1
sta     _joypad1
;
; }
;
rts
Работает одинаково и с volatile и без него.
Офтоп: У меня гугл хром, странно, что у меня не работает растягивание окна. Очень неудобно так оформлять ответ.

Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5182
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #17 : 24 Февраль 2023, 19:02:52 »
Работает одинаково и с volatile и без него.
Теперь вы изменили код (убрали temp) и конечно нет смысла сравнивать, он не будет делать оптимизации в любом случае и работает правильно всегда, нужно сравнить прошлый код, где у вас не читалась одна кнопка (не кнопка A, я ошибся, там вообще должно было быть по-другому из-за смещения кнопок, не знаю как вы не заметили). Если вы определяете JOYPAD1, то volatile обязан быть и ваш косяк.

Оффлайн DGanger

  • Пользователь
  • Сообщений: 84
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #18 : 24 Февраль 2023, 19:20:33 »
Sharpnull, а в случае с разработкой для NES вообще есть какие-нибудь преимущества у Си перед Ассемблером, как считаете?

Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5182
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #19 : 24 Февраль 2023, 20:28:30 »
DGanger, не нужно быть экспертом для ответа: на C (Си) проще писать, а это экономит время разработки, включая исправление ошибок. Так и в современной разработке используют новые тяжёлые тормозящие технологии, но позволяющие быстро писать. Только, в любом случае, когда проект пишется "хорошо" (профессионально), приходится знать что под капотом, а в отношении NES это C для всего, кроме критичного кода где уже ASM. Кто хочет выжать максимум или любит копаться, тот выбирает ASM.
Поэтому я не понял зачем переписывать чтение контроллера, это просто написать ASM и в некоторых (я знаю только один, мало опыта) каркасах для разработки на C уже есть функция получения ввода с контроллера, которая возвращает заранее прочитанный ввод (чтобы не тратить время на чтение каждый раз, больше 1 раза за 1 кадр не нужно).

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #20 : 25 Февраль 2023, 11:52:20 »
Sharpnull, значит я как-то некорректно тестировал. Но обратно менять уже не буду.
Я тут посмотрел другие сгенерированные компилятором функции из Си кода. Там вообще ужас, где происходит обращение к полям структур (я думал там адресация примерно как с массивами, если поля одинаковой размерности, но нет), так что в моем случае гнаться за хорошей оптимизацией особо смысла нет.
Если я решу заняться полноценной оптимизацией, придется выкидывать все структуры (а их много, ибо приходится хранить много  параметров игровых объектов), а это равноценно переписыванию половины проекта.
Изначально я старался сделать хорошую архитектуру проекта, насколько это позволяет Си и мои навыки программиста. Щас уже, конечно, проект разросся и не так красиво все выглядит, но если демка игры зайдет, мб буду рефакторить все. Раньше времени закапываться в перфекционизм не хочется.

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #21 : 25 Февраль 2023, 15:21:28 »
Поймал очень странный баг сейчас. И снова у меня нет идей в чем проблема. Есть такая функция:
void draw_weapon_submenu (void) {
    // Указываем адрес первого симовла в таблицу имен
    PPU_ADDRESS = HIGH_BYTE_MENU_ADDR; 
    PPU_ADDRESS = submenu_items_addr [0];
    // Берем адрес начала массива
    p_text = weapons [p_selected_mech->r_arm_weapon_index_].name_; 
    // Выводим оружие правой руки
    for (i = 0; i < PART_NAME_SIZE; ++i) {
PPU_DATA = p_text [i];
}
   
    PPU_ADDRESS = HIGH_BYTE_MENU_ADDR;
    PPU_ADDRESS = submenu_items_addr [1];
    p_text = weapons [mechs [index_selected_mech].l_arm_weapon_index_].name_;
    // Выводим оружие левой руки руки
    for (i = 0; i < PART_NAME_SIZE; ++i) {
PPU_DATA = p_text [i];
}
}
Если заменяю
p_text = weapons [mechs [index_selected_mech].l_arm_weapon_index_].name_;
на любой другой способ обращения к элементу массива weapons  по индексу (например, weapons
  • или weapons [p_selected_mech->r_arm_weapon_index_],

То игра падает. Причем первое оружие выводится нормально через указатель, а второе выводится нормально только через обращение к массиву мехов.
Прикрепляю рабочий (первый) и нерабочий вариант сгенерированного кода в виде файлов. Такое ощущение, что разные способы обращению ломают режим вывода, но даже не могу представить как оно может влиять.
PS: Если вызывать функцию в других местах, то функция работает в любых вариантах.
« Последнее редактирование: 25 Февраль 2023, 15:33:16 от Howard Phillips »

Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5182
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #22 : 25 Февраль 2023, 18:41:53 »
Прикрепляю рабочий (первый) и нерабочий вариант сгенерированного кода в виде файлов
Здесь ничего не скажешь, у 2-го варианта код p_text = weapons [p_selected_mech->l_arm_weapon_index_].name_ такой же как выше для p_selected_mech->r_arm_weapon_index_. В вашем случае в PPU вы должны записывать с выключенным рендером (откл. спрайты и фон) или в течение VBlank, а здесь вы можете не успевать, но тогда проблема была бы в 1-м варианте, он медленнее. Вообще принято записывать в буфер (в RAM, где адреса и данные для записи), откуда в VBlank происходит запись в PPU. Нужно смотреть ром в эмуляторе, может станет понятнее.
Там вообще ужас, где происходит обращение к полям структур (я думал там адресация примерно как с массивами, если поля одинаковой размерности, но нет)
Наверно сложно сделать как обычно в играх для NES: на каждый атрибут/поле фиксированный массив и обращение просто как LDX #индекс_объекта | LDA начало_поля,X.

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #23 : 25 Февраль 2023, 19:21:17 »
Sharpnull, спасибо, что  глянули файлы.  Функцию вызывал при выключенном экране. Мб у меня какое-то неудачное для эмулятора сочетание команд получается именно в том месте, хотя я использовал подобные вызовы в других местах - там все нормально отрабатывает. Пусть пока будет вариант менее оптимальный, работает и ладно.

Цитата
Наверно сложно сделать как обычно в играх для NES: на каждый атрибут/поле фиксированный массив и обращение просто как LDX #индекс_объекта | LDA начало_поля,X.
Сделать несложно, просто тогда код получается плохо читаемый, придется держать в уме какой элемент массива, что обозначает. Можно, как вариант, сделать именованные индексы через enum'ы, но не уверен, что это красиво будет. В следующем проекте мб попробую избавиться от структур.

Цитата
Вообще принято записывать в буфер (в RAM, где адреса и данные для записи), откуда в VBlank происходит запись в PPU.
Т.е. обычно выделяют память под клон таблицы имен и периодически выгружают его в PPU?

И еще есть дурацкий вопрос по сс65 компилятору. Как узнать сколько ROM я уже занял, я гуглил, но ничего не смог найти. В списке ключей ничего такого не смог найти.

Офтоп: Попробовал в опере растягивать форму ввода - тоже самое, ничего не происходит. Везет мне на странности.

Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5182
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #24 : 25 Февраль 2023, 20:34:15 »
Сделать несложно, просто тогда код получается плохо читаемый
Я про компилятор, почему он не может разложить структуру, может где-то удобнее.
Т.е. обычно выделяют память под клон таблицы имен и периодически выгружают его в PPU?
Не только NameTable и Attribute Table, и графику, если CHR RAM. Это во время вкл. экрана. Мотивация такая: VBlank короткий, можно во время рендера/рисования (вне VBlank) высчитать что нужно заменить, а в VBlank только запись в PPU. Это не всегда нужно и может только отнимать время, например, для палитры могут отвести своё место в RAM, вне VBlank заполнить RAM цветами и поставить флаг, в VBlank проверить флаг и записать в PPU, если нужно.
Как узнать сколько ROM я уже занял
Из простых способов: добавить в конце всех данных и кода какую-нибудь уникальную строчку и написать скрипт, который будет считать. Есть будет переполнение, вы всё равно узнаете. Можете смотреть в эмуляторе или HEX-редакторе будет видно.

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #25 : 25 Февраль 2023, 20:47:19 »
Sharpnull,
Цитата
Не только NameTable и Attribute Table, и графику, если CHR RAM. Это во время вкл. экрана. Мотивация такая: VBlank короткий, можно во время рендера/рисования (вне VBlank) высчитать что нужно заменить, а в VBlank только запись в PPU. Это не всегда нужно и может только отнимать время, например, для палитры могут отвести своё место в RAM, вне VBlank заполнить RAM цветами и поставить флаг, в VBlank проверить флаг и записать в PPU, если нужно.
Значит весь код, который не обращается к PPU, корректно отрабатывает вне VBlank? Я сейчас все расчеты делаю только во время VBlank или выключаю экран, если код долгий.

Цитата
Из простых способов: добавить в конце всех данных и кода какую-нибудь уникальную строчку и написать скрипт, который будет считать. Есть будет переполнение, вы всё равно узнаете. Можете смотреть в эмуляторе или HEX-редакторе будет видно.
Я надеялся, что есть какой-то штатный способ. Значит буду просто ждать переполнения :). Интересный, конечно, опыт получается с разработкой под NES, до этого больших проектов на си не писал и тем более под непопулярные компиляторы. Еще чувствую, что будут веселые пляски с созданием картриджа, наверняка куча граблей вылезет.

Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5182
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #26 : 25 Февраль 2023, 21:00:21 »
Значит весь код, который не обращается к PPU, корректно отрабатывает вне VBlank?
Конечно, код всегда выполняется, а IRQ обычно нужно для графических эффектов, чтобы в точном месте экрана во время рендера произвести запись в регистры PPU или сменить банки графики. Есть разные подходы к разработке относительно того где выполнять код: основной поток или в обработчике NMI (который начинается с VBlank, но может выполняться на протяжении всего кадра). Здесь об этом: https://www.nesdev.org/wiki/NMI_thread.
UPD: Забыл сказать, что иногда отключают экран до начала VBlank, чтобы в это время писать в PPU, т. к. часто всё равно не видно верхние и нижние 8 пикселей экрана. Наверно также можно писать и после VBlank, перед включением экрана. В 1-м случае без IRQ будет сложно отключать экран вовремя, а во 2-м случае нужно тоже не превысить. В коммерческих играх довольно часто в этих 8 пикселях сверху и снизу артефакты, потому что не должно быть видно.
« Последнее редактирование: 25 Февраль 2023, 21:07:35 от Sharpnull »

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #27 : 26 Февраль 2023, 18:08:24 »
Sharpnull, давно у меня был вопрос по верхним 8 пикселям.
Почему в регионе PAL по адресу 2000 (и на всю верхнюю строку)  спрайты фона выводятся нормально, а спрайты из OAM не выводятся (исчезает часть метатайла, которая туда попадает)?
И еще в левом верхнем углу у меня отображается нулевой спрайт (из адреса 0х00) из второй страницы CHR, хотя фоны у меня настроены на первую страницу CHR, а спрайты для ОАМ вывожу со второй страницы CHR.

Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5182
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #28 : 26 Февраль 2023, 19:12:13 »
Почему в регионе PAL по адресу 2000 (и на всю верхнюю строку)  спрайты фона выводятся нормально, а спрайты из OAM не выводятся (исчезает часть метатайла, которая туда попадает)?
Под "спрайтами фона" вы наверно имеете в виду просто фон, а "спрайты из OAM" - это единственные спрайты, которые есть, не нужно дописывать "из OAM". А "метатайл" я понимаю как "метаспрайт" - несколько спрайтов логически объединённых. Не знаю зачем приписка PAL, в других "регионах" также.
Спрайт нельзя отобразить выше, чем с координаты Y равно 1, при этом будет в OAM будет записано 00, поэтому нельзя отобразить часть спрайта на верхней границе как и слева (https://www.nesdev.org/wiki/PPU_OAM#Byte_0). Это ещё одна причина игнорировать отображение верхних 8 пикселей экрана.
И еще в левом верхнем углу у меня отображается нулевой спрайт (из адреса 0х00) из второй страницы CHR, хотя фоны у меня настроены на первую страницу CHR, а спрайты для ОАМ вывожу со второй страницы CHR.
Не понял. У вас спрайты со 2-й страницы CHR и нулевой спрайт из 2-й страницы как должно быть, а фон не важно с какой страницы, они могут быть с одной страницы. Когда пишите "адрес", лучше уточняйте как-то так: CPU $xxxx (или RAM $xxxx, что совпадает для RAM с CPU), PPU $xxxx, PRG $xxxxxx (относительно PRG ROM без заголовка iNES), CHR $xxxxxx (относительно CHR ROM без заголовка iNES), File $xxxxxx (относительно начала файла, т. е. с заголовком).

Оффлайн Howard Phillips

  • Пользователь
  • Сообщений: 32
    • Просмотр профиля
Хранение клона OAM таблицы в PRG-ROM NES
« Ответ #29 : 01 Март 2023, 11:12:23 »
Sharpnull,
Цитата
Не понял. У вас спрайты со 2-й страницы CHR и нулевой спрайт из 2-й страницы как должно быть, а фон не важно с какой страницы, они могут быть с одной страницы. Когда пишите "адрес", лучше уточняйте как-то так: CPU $xxxx (или RAM $xxxx, что совпадает для RAM с CPU), PPU $xxxx, PRG $xxxxxx (относительно PRG ROM без заголовка iNES), CHR $xxxxxx (относительно CHR ROM без заголовка iNES), File $xxxxxx (относительно начала файла, т. е. с заголовком).
Я понимаю, что спрайты и фон могут быть с одной страницы в один момент времени. Я уточнил, что у меня настроены они на разные страницы, так как в левом верхнем углу выводится фон из первой страницы, хотя для фонов у меня выбрана вторая страница все время выполнения программы. Страницу для фонов я не переключаю.
Цитата
weapons [p_selected_mech->l_arm_weapon_index_].name_
Снова столкнулся с багом при таком обращении к элементу массива. Нашел закономерность, что графика падает, если такое обращение было сделано при выключенном экране, а если обращаться при включенном экране, то все нормально.

И если разворачиваю цикл вывода в этой функции:
void draw_weapon_submenu (void) {
    // Указываем адрес первого симовла в таблицу имен
    PPU_ADDRESS = HIGH_BYTE_MENU_ADDR; 
    PPU_ADDRESS = submenu_items_addr [0];
    // Берем адрес начала массива
    p_text = weapons [p_selected_mech->r_arm_weapon_index_].name_; 
    // Выводим оружие правой руки
    for (i = 0; i < PART_NAME_SIZE; ++i) {
PPU_DATA = p_text [i];
}
   
    PPU_ADDRESS = HIGH_BYTE_MENU_ADDR;
    PPU_ADDRESS = submenu_items_addr [1];
    // Если заменить эту строку на обращение через указатель, графика падает
    p_text = weapons [mechs [index_selected_mech].l_arm_weapon_index_].name_;
    // Выводим оружие левой руки руки
    for (i = 0; i < PART_NAME_SIZE; ++i) {
PPU_DATA = p_text [i];
}
}

Вот так:
void draw_weapon_submenu (void) {
    // Указываем адрес первого симовла в таблицу имен
    PPU_ADDRESS = HIGH_BYTE_MENU_ADDR; 
    PPU_ADDRESS = submenu_items_addr [0];
    // Берем адрес начала массива
    p_text = weapons [p_selected_mech->r_arm_weapon_index_].name_; 
    // Выводим оружие правой руки
    PPU_DATA = p_text [0];
    PPU_DATA = p_text [1];
    PPU_DATA = p_text [2];
    PPU_DATA = p_text [3];
    PPU_DATA = p_text [4];
    PPU_DATA = p_text [5];
    PPU_DATA = p_text [6];
    PPU_DATA = p_text [7];
    PPU_DATA = p_text [8];
    PPU_DATA = p_text [9];
   
    PPU_ADDRESS = HIGH_BYTE_MENU_ADDR;
    PPU_ADDRESS = submenu_items_addr [1];
    // Если заменить эту строку на обращение через указатель, графика падает
    p_text = weapons [mechs [index_selected_mech].l_arm_weapon_index_].name_;
    // Выводим оружие левой руки руки
    for (i = 0; i < PART_NAME_SIZE; ++i) {
PPU_DATA = p_text [i];
}
}
Графика тоже падает. Какое-то шаманство, очень напрягают такие моменты. почти одинаковый код, а поведение в чем-то принциально отличается. Неужели это компилятор такой проблемный?
« Последнее редактирование: 01 Март 2023, 11:19:37 от Howard Phillips »