Разработка и ромхакинг > Разработка игр

Хранение клона OAM таблицы в PRG-ROM NES

<< < (2/3) > >>

Howard Phillips:
Sharpnull, значит я как-то некорректно тестировал. Но обратно менять уже не буду.
Я тут посмотрел другие сгенерированные компилятором функции из Си кода. Там вообще ужас, где происходит обращение к полям структур (я думал там адресация примерно как с массивами, если поля одинаковой размерности, но нет), так что в моем случае гнаться за хорошей оптимизацией особо смысла нет.
Если я решу заняться полноценной оптимизацией, придется выкидывать все структуры (а их много, ибо приходится хранить много  параметров игровых объектов), а это равноценно переписыванию половины проекта.
Изначально я старался сделать хорошую архитектуру проекта, насколько это позволяет Си и мои навыки программиста. Щас уже, конечно, проект разросся и не так красиво все выглядит, но если демка игры зайдет, мб буду рефакторить все. Раньше времени закапываться в перфекционизм не хочется.

Howard Phillips:
Поймал очень странный баг сейчас. И снова у меня нет идей в чем проблема. Есть такая функция:

--- Код: ---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 [0] или weapons [p_selected_mech->r_arm_weapon_index_],
То игра падает. Причем первое оружие выводится нормально через указатель, а второе выводится нормально только через обращение к массиву мехов.
Прикрепляю рабочий (первый) и нерабочий вариант сгенерированного кода в виде файлов. Такое ощущение, что разные способы обращению ломают режим вывода, но даже не могу представить как оно может влиять.
PS: Если вызывать функцию в других местах, то функция работает в любых вариантах.

Sharpnull:

--- Цитата: Howard Phillips от 25 Февраль 2023, 15:21:28 ---Прикрепляю рабочий (первый) и нерабочий вариант сгенерированного кода в виде файлов
--- Конец цитаты ---
Здесь ничего не скажешь, у 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. Нужно смотреть ром в эмуляторе, может станет понятнее.

--- Цитата: Howard Phillips от 25 Февраль 2023, 11:52:20 ---Там вообще ужас, где происходит обращение к полям структур (я думал там адресация примерно как с массивами, если поля одинаковой размерности, но нет)
--- Конец цитаты ---
Наверно сложно сделать как обычно в играх для NES: на каждый атрибут/поле фиксированный массив и обращение просто как LDX #индекс_объекта | LDA начало_поля,X.

Howard Phillips:
Sharpnull, спасибо, что  глянули файлы.  Функцию вызывал при выключенном экране. Мб у меня какое-то неудачное для эмулятора сочетание команд получается именно в том месте, хотя я использовал подобные вызовы в других местах - там все нормально отрабатывает. Пусть пока будет вариант менее оптимальный, работает и ладно.


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


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

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

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

Sharpnull:

--- Цитата: Howard Phillips от 25 Февраль 2023, 19:21:17 ---Сделать несложно, просто тогда код получается плохо читаемый
--- Конец цитаты ---
Я про компилятор, почему он не может разложить структуру, может где-то удобнее.
--- Цитата: Howard Phillips от 25 Февраль 2023, 19:21:17 ---Т.е. обычно выделяют память под клон таблицы имен и периодически выгружают его в PPU?
--- Конец цитаты ---
Не только NameTable и Attribute Table, и графику, если CHR RAM. Это во время вкл. экрана. Мотивация такая: VBlank короткий, можно во время рендера/рисования (вне VBlank) высчитать что нужно заменить, а в VBlank только запись в PPU. Это не всегда нужно и может только отнимать время, например, для палитры могут отвести своё место в RAM, вне VBlank заполнить RAM цветами и поставить флаг, в VBlank проверить флаг и записать в PPU, если нужно.
--- Цитата: Howard Phillips от 25 Февраль 2023, 19:21:17 ---Как узнать сколько ROM я уже занял
--- Конец цитаты ---
Из простых способов: добавить в конце всех данных и кода какую-нибудь уникальную строчку и написать скрипт, который будет считать. Есть будет переполнение, вы всё равно узнаете. Можете смотреть в эмуляторе или HEX-редакторе будет видно.

Howard Phillips:
Sharpnull,

--- Цитата ---Не только NameTable и Attribute Table, и графику, если CHR RAM. Это во время вкл. экрана. Мотивация такая: VBlank короткий, можно во время рендера/рисования (вне VBlank) высчитать что нужно заменить, а в VBlank только запись в PPU. Это не всегда нужно и может только отнимать время, например, для палитры могут отвести своё место в RAM, вне VBlank заполнить RAM цветами и поставить флаг, в VBlank проверить флаг и записать в PPU, если нужно.
--- Конец цитаты ---
Значит весь код, который не обращается к PPU, корректно отрабатывает вне VBlank? Я сейчас все расчеты делаю только во время VBlank или выключаю экран, если код долгий.


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

Sharpnull:

--- Цитата: Howard Phillips от 25 Февраль 2023, 20:47:19 ---Значит весь код, который не обращается к PPU, корректно отрабатывает вне VBlank?
--- Конец цитаты ---
Конечно, код всегда выполняется, а IRQ обычно нужно для графических эффектов, чтобы в точном месте экрана во время рендера произвести запись в регистры PPU или сменить банки графики. Есть разные подходы к разработке относительно того где выполнять код: основной поток или в обработчике NMI (который начинается с VBlank, но может выполняться на протяжении всего кадра). Здесь об этом: https://www.nesdev.org/wiki/NMI_thread.
UPD: Забыл сказать, что иногда отключают экран до начала VBlank, чтобы в это время писать в PPU, т. к. часто всё равно не видно верхние и нижние 8 пикселей экрана. Наверно также можно писать и после VBlank, перед включением экрана. В 1-м случае без IRQ будет сложно отключать экран вовремя, а во 2-м случае нужно тоже не превысить. В коммерческих играх довольно часто в этих 8 пикселях сверху и снизу артефакты, потому что не должно быть видно.

Howard Phillips:
Sharpnull, давно у меня был вопрос по верхним 8 пикселям.
Почему в регионе PAL по адресу 2000 (и на всю верхнюю строку)  спрайты фона выводятся нормально, а спрайты из OAM не выводятся (исчезает часть метатайла, которая туда попадает)?
И еще в левом верхнем углу у меня отображается нулевой спрайт (из адреса 0х00) из второй страницы CHR, хотя фоны у меня настроены на первую страницу CHR, а спрайты для ОАМ вывожу со второй страницы CHR.

Sharpnull:

--- Цитата: Howard Phillips от 26 Февраль 2023, 18:08:24 ---Почему в регионе PAL по адресу 2000 (и на всю верхнюю строку)  спрайты фона выводятся нормально, а спрайты из OAM не выводятся (исчезает часть метатайла, которая туда попадает)?
--- Конец цитаты ---
Под "спрайтами фона" вы наверно имеете в виду просто фон, а "спрайты из OAM" - это единственные спрайты, которые есть, не нужно дописывать "из OAM". А "метатайл" я понимаю как "метаспрайт" - несколько спрайтов логически объединённых. Не знаю зачем приписка PAL, в других "регионах" также.
Спрайт нельзя отобразить выше, чем с координаты Y равно 1, при этом будет в OAM будет записано 00, поэтому нельзя отобразить часть спрайта на верхней границе как и слева (https://www.nesdev.org/wiki/PPU_OAM#Byte_0). Это ещё одна причина игнорировать отображение верхних 8 пикселей экрана.

--- Цитата: Howard Phillips от 26 Февраль 2023, 18:08:24 ---И еще в левом верхнем углу у меня отображается нулевой спрайт (из адреса 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:
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];
}
}

--- Конец кода ---
Графика тоже падает. Какое-то шаманство, очень напрягают такие моменты. почти одинаковый код, а поведение в чем-то принциально отличается. Неужели это компилятор такой проблемный?

Sharpnull:

--- Цитата: Howard Phillips от 01 Март 2023, 11:12:23 ---так как в левом верхнем углу выводится фон из первой страницы, хотя для фонов у меня выбрана вторая страница все время выполнения программы
--- Конец цитаты ---
Вы писали, что у вас в левом верхнем углу спрайт, а не фон, и страницы наоборот. Ладно, запускайте Mesen и смотрите, там просто понять откуда и что берётся. Скорее всего у вас какие-то спрайты (настоящие железные) имеют данные 00 xx xx 00 по адресам CPU $0200-02FF и они туда съехали, нужно у неиспользованных спрайтов ставить координату Y (готовое значение) от EF до FF.

--- Цитата: Howard Phillips от 01 Март 2023, 11:12:23 ---Какое-то шаманство, очень напрягают такие моменты. почти одинаковый код, а поведение в чем-то принциально отличается.
--- Конец цитаты ---
В прошлый раз код был правильный в обоих случаях в том месте (стоило проверить в эмуляторе). Проблема может быть в другом месте. Проблемы появляющиеся только из-за порядка кода или аналогичного кода могут быть из-за забытого где-то volatile и происходит оптимизация или что-то такое. Возможно листинг, который вы показывали, изменяется из-за последующих оптимизаций. Для проверки такого нужно отключить оптимизацию насколько возможно. У вас это убрать флаги -O и может что-то ещё.
UPD:

--- Цитата: Howard Phillips от 01 Март 2023, 11:12:23 ---Неужели это компилятор такой проблемный?
--- Конец цитаты ---
cc65 обновляется постоянно (последнее изменение Feb 26, 2023), можно попробовать новую версию, но может получится, что проблема будет просто отсрочена и вылезет при другом коде.

Howard Phillips:

--- Цитата ---Вы писали, что у вас в левом верхнем углу спрайт, а не фон, и страницы наоборот. Ладно, запускайте Mesen и смотрите, там просто понять откуда и что берётся. Скорее всего у вас какие-то спрайты (настоящие железные) имеют данные 00 xx xx 00 по адресам CPU $0200-02FF и они туда съехали, нужно у неиспользованных спрайтов ставить координату Y (готовое значение) от EF до FF.
--- Конец цитаты ---
Тут я еще не привык к терминологии. Я изучал разработку под NES по разрозненным статьям, а там немного разная терминология везде (тем более многие статьи переводные).
По спрайтами я подразумеваю любые атомарные блоки 8х8 из CHR.
И вы оказались правы о природе артефакта в левом верхнем углу. Я перед запуском все спрайты инициализировал пустым спрайтом и вывел за экран по адресу Y = 0xFF и артефакт пропал. Спасибо, что помогаете.

PS. Попробовал отключить оптимизацию (был ключ -Oi) и без оптимизации баг пропал.

Sharpnull:

--- Цитата: Howard Phillips от 01 Март 2023, 19:01:53 ---По спрайтами я подразумеваю любые атомарные блоки 8х8 из CHR.
--- Конец цитаты ---
Не представляю кто такое мог написать. Неразделимый блок графики всегда был "тайлом" (tile), обычно 8x8 px. "Спрайт" (sprite) состоит из тайлов, в отношении старых систем это именно аппаратные спрайты (бывало без них, печальное зрелище), у NES они 8x8 или 8x16 px, а у Mega Drive можно большего размера.
Я тоже не очень хорошо знаю терминологию, например "фоном" (background) можно назвать всю область без спрайтов, который включает в себя один или больше nametable, причём он включает attribute table, но как назвать область только из индексов тайлов. Nametable похож на более обширный термин - tilemap, а CHR получается tileset, при этом pattern tables это область PPU $0000-1FFF, а не сама графика (?).

--- Цитата: Howard Phillips от 01 Март 2023, 19:01:53 ---PS. Попробовал отключить оптимизацию (был ключ -Oi) и без оптимизации баг пропал.
--- Конец цитаты ---
Как и говорил, вероятно удалилось что-то важное.

Howard Phillips:
Здравствуйте. Снова я возвращаюсь с дурацким вопросом, но сам я решить проблему не смог, потому жду помощи.
У меня хранятся палитры вот в таком видео:

--- Код: ---const unsigned char PALETTE[]={
0x0F, 0x17, 0x28, 0x38,  0x0F, 0x38, 0x29, 0x0A,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, // Палитры фонов
0x0F, 0x17, 0x28, 0x38,  0x0A, 0x13, 0x23, 0x33,  0x0F, 0x07, 0x27, 0x39,  0x00, 0x00, 0x00, 0x00}; // Палитры спрайтов
// А вот функция загрузки палитр
void Load_Palette (void) {
PPU_ADDRESS = 0x3f;
PPU_ADDRESS = 0x00;
for( i = 0; i < sizeof(PALETTE); ++i ){
PPU_DATA = PALETTE[i];
}
}

--- Конец кода ---
Нулевой элемент массива PALETTE должен задавать цвет фона для нулевой палитры фона по адресу 0x3f00. Но у меня почему-то этот адрес никак не влияет на цвет фона, а цвет фона меняется нулевым байтом палитры спрайтов -по адресу $3F10.
Но нулевые цвета палитр спрайтов прозрачные и записанные там коды ни на что не должны влиять. Я снова что-то не понимаю? Почему запись в 0x3f00 не меняет цвет фона, а запись в $3F10 меняет универсальный фон?
Оптимизитор пробовал отключать - это не помогло.

PS: Демку игры я доделал, щас уже занимаюсь расширением скелета игры, добавляю фичи.

Sharpnull:

--- Цитата: Howard Phillips от 31 Март 2023, 00:02:57 ---Но нулевые цвета палитр спрайтов прозрачные и записанные там коды ни на что не должны влиять. Я снова что-то не понимаю? Почему запись в 0x3f00 не меняет цвет фона, а запись в $3F10 меняет универсальный фон?
--- Конец цитаты ---
Как написано здесь https://www.nesdev.org/wiki/PPU_palettes, адреса $3F10/$3F14/$3F18/$3F1C это зеркала $3F00/$3F04/$3F08/$3F0C, поэтому изменение $3F10 равносильно изменению $3F00, а вот $3F04/$3F08/$3F0C и $3F14/$3F18/$3F1C в обычных условиях не влияют на картинку.
Вы уверены, что запись в $3F00 не меняет цвет? Т. е. вы должны были убрать запись в $3F10. Ещё перед записью в PPU нужно делать BIT $2002 (или LDA $2002) (UPD: Это вроде не нужно, если ранее такое было).

Какое совпадение, что 29 марта 2023 добавили информацию об очередном баге в железе: https://www.nesdev.org/wiki/PPU_registers#Palette_corruption. (UPD4: Неправильно сначала написал) Оказывается после записи палитры нужно сделать так (как в некоторых играх):

--- Код: ---  lda #$3F
  sta PPUADDR
  lda #0
  sta PPUADDR
  sta PPUADDR
  sta PPUADDR
--- Конец кода ---
Например в Battle City - CPU $D50E:

--- Код: ---  ASL A
  ASL A
  ASL A
  ASL A
  TAX
  LDY #$10
  LDA #$3F
  STA PpuAddr_2006
  LDA #$00
  STA PpuAddr_2006
D51F:
  LDA $D565,X
  STA PpuData_2007
  INX
  DEY
  BNE $D51F
  LDA #$FF
  STA $4D
  ; Обход бага
  LDA #$3F
  STA PpuAddr_2006
  LDA #$00
  STA PpuAddr_2006
  STA PpuAddr_2006
  STA PpuAddr_2006
  RTS

--- Конец кода ---

Я что-то похожее видел в коде лицензионных игр, кучу каких-то странных записей типа этого, но в эмуляторе проблемы не будет.
UPD2: Например, во время VBlank в Teenage Mutant Ninja Turtles II - The Arcade Game (U) [!] по адресу CPU $F998:

--- Код: ---  LDA PpuStatus_2002
  LDA #$00
  STA PpuScroll_2005
  STA PpuScroll_2005
  LDA PpuStatus_2002
  LDA #$FF
  STA $C000
  STA $C001
  LDA #$00
  STA PpuAddr_2006
  STA PpuAddr_2006
  LDA #$10
  STA PpuAddr_2006
  STA PpuAddr_2006
  LDA #$00
  STA PpuAddr_2006
  STA PpuAddr_2006
  LDA #$10
  STA PpuAddr_2006
  STA PpuAddr_2006
  LDA #$00
  STA PpuAddr_2006
  STA PpuAddr_2006

--- Конец кода ---
Teenage Mutant Ninja Turtles III - The Manhattan Project (U) [!] - CPU $F564:

--- Код: ---  LDA PpuStatus_2002
  LDX #$00
  STX PpuScroll_2005
  STX PpuScroll_2005
  LDA PpuStatus_2002
  LDA #$FF
  STA $C000
  STA $C001
  STX PpuAddr_2006
  STX PpuAddr_2006
  LDY #$10
  STY PpuAddr_2006
  STY PpuAddr_2006
  STX PpuAddr_2006
  STX PpuAddr_2006
  STY PpuAddr_2006
  STY PpuAddr_2006
  STX PpuAddr_2006
  STX PpuAddr_2006
--- Конец кода ---
UPD3: В обоих случаях дальше нет записи в $2007.
UPD5: Почитал обсуждение, странные записи в TMNTII и TMNTIII нужны для прерываний в MMC3: https://forums.nesdev.org/viewtopic.php?p=281099#p281099. А про Palette corruption не понял, если записывать по 16 байт в палитру (там пишут про $3F10-3F1F), то вроде исправление не нужно и разработчики игр вставили на основе советов от Nintendo на всякий случай, чтобы проверить нужны старые ревизии, всё плохо.

Howard Phillips:
Вот год функции загрузки палитры после компиляции

--- Код: ---; void __near__ Load_Palette (void)
; ---------------------------------------------------------------

.segment "CODE"

.proc _Load_Palette: near

.segment "CODE"

;
; PPU_ADDRESS = 0x3f;
;
lda     #$3F
sta     $2006
;
; PPU_ADDRESS = 0x00;
;
lda     #$00
sta     $2006
;
; for( i = 0; i < sizeof(PALETTE); ++i ){
;
sta     _i
L0007: lda     _i
cmp     #$20
bcs     L0003
;
; PPU_DATA = PALETTE[i];
;
ldy     _i
lda     _PALETTE,y
sta     $2007
;
; for( i = 0; i < sizeof(PALETTE); ++i ){
;
inc     _i
jmp     L0007
;
; }
;
L0003: rts

.endproc
--- Конец кода ---
А вот массив:

--- Код: ---const unsigned char PALETTE[]={
0x0A, 0x17, 0x28, 0x38,  0x0F, 0x38, 0x29, 0x0A,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, // Палитры фонов
0x0F, 0x31, 0x21, 0x02,  0x0A, 0x13, 0x23, 0x33,  0x0F, 0x07, 0x27, 0x39,  0x00, 0x00, 0x00, 0x00}; // Палитры спрайтов

--- Конец кода ---
0x0A или не записывается или записывается, но не меняет цвет. А если после вызова функции я меняю цвет по адресу $3F00 вот так:

--- Код: ---All_Off(); // turn off screen

Reset_Scroll ();
reset_map ();
reset_OAM ();
Load_Palette();

PPU_ADDRESS = 0x3f;
PPU_ADDRESS = 0x00;
PPU_DATA = PALETTE [0];
All_On ();

--- Конец кода ---

То цвет меняется нормально. Ассемблерный код нормальный, должен цвет меняться, но как-то неправильно функция работает судя по всему. Странно это все.

Sharpnull:

--- Цитата: Howard Phillips от 31 Март 2023, 19:54:26 ---А вот массив
--- Конец цитаты ---
Я же писал, что $3F00 в который вы записываете 0A и $3F10 (у вас = 0F) - зеркала, это значит, что изменение $3F00 изменяет одновременно $3F10 и наоборот. Поэтому ставьте в массиве для $3F00 и $3F10 одинаковые значения. UPD: Не понял почему раньше вы писали про нулевой байт, ну попробуйте 0-й цвет для каждой палитры (4 байта) менять на один и тот же цвет, как обычно и делают.

Howard Phillips:
Точно, я понял зеркала, но до конца не довел мысль в голове, что записывая весь массив, я два раза меня цвет фона. Спасибо за пояснение.

Под нулевым байтом я подразумевал нулевой байт палитр, неудачно сформулировал.

Howard Phillips:
И снова я с дурацкими вопросами. Игра приближается к относительно законченной версии и начал немного убирать баги. Вопрос следующий.
Когда мне нужно изменить большое количество тайлов фона, я отключаю экран, редактирую фон и включаю экран обратно.
Но в момент включения экрана отрисовка первого кадра  начинается не с левого верхнего угла как положено, а из случайной части экрана и получается что-то вроде рывка всего фона, но в следующем кадре уже все нормально. Такая тряска экрана выглядит некрасиво, хотелось бы чтоб никаких рывков фона не было.
Вот код включения и выключения экрана:

--- Код: ---void All_Off (void) {
PPU_CTRL = 0;
PPU_MASK = 0;
}


void All_On (void) {
PPU_CTRL = 0x90; /* screen is on, NMI on 0b 1001 0000
                                                |||| ||||
                                                |||| ||++- Выбор базовой таблицы имен
                                                |||| ||    (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
                                                |||| |+--- VRAM address increment per CPU read/write of PPUDATA
                                                |||| |     (0: add 1, going across; 1: add 32, going down)
                                                |||| +---- Sprite pattern table address for 8x8 sprites
                                                ||||       (0: $0000; 1: $1000; ignored in 8x16 mode)
                                                |||+------ Background pattern table address (0: $0000; 1: $1000)
                                                ||+------- Sprite size (0: 8x8 pixels; 1: 8x16 pixels – see PPU OAM#Byte 1)
                                                |+-------- PPU master/slave select
                                                |          (0: read backdrop from EXT pins; 1: output color on EXT pins)
                                                +--------- Generate an NMI at the start of the
                                                        vertical blanking interval (0: off; 1: on)
                     */

PPU_MASK = 0x1E; // 0b 0001 1110
                           // | ||||
                           // | |||+ - включает режим в оттенках серого
                           // | ||+ - включает показ фона в крайнем левом стобце
                           // | |+ - включает показ спрайтов в крайнем левом столбце
                           // | + - включает показ фона
                           // + - включает показ спрайтов
}

--- Конец кода ---

Пробовал обнулять PPU_ADDRESS перед включением, но это не помогло.
Еще видел совет, что надо ждать прерывания конца отрисовки кадра перед включением экрана. Но при выключенном экране оно не работает.
Как мне корректно включать экран? Спасибо.

Sharpnull:

--- Цитата: Howard Phillips от 16 Апрель 2023, 17:43:59 ---Еще видел совет, что надо ждать прерывания конца отрисовки кадра перед включением экрана. Но при выключенном экране оно не работает.
--- Конец цитаты ---
Да, включать экран нужно в VBlank. Для этого нужно синхронизировать код. Можно не отключать генерацию NMI, когда экран выключен, в этом случае можно выделить байт в RAM для записи в PPUCTRL, тогда вне обработчика NMI вы изменяете байт в RAM, а в NMI будет реальная запись в PPUCTRL. Такие переменные/адреса называются "shadow" что-то там (UPD: или Pseudo-registers как в FDS BIOS: https://www.nesdev.org/wiki/FDS_BIOS). Другой вариант с включённым NMI, это ожидать VBlank, когда в NMI происходит установка или инкремент значения в RAM, которое в основном потоке проверяется на изменение, наверно у вас такой вариант и он не работает из-за откл. NMI. Остаётся ждать VBlank как после запуска игры, что не рекомендуется если у вас вкл. NMI:

--- Код: ---@vblankwait:
    BIT $2002 ; или LDA $2002
    BPL @vblankwait
--- Конец кода ---
UPD2: Отключать экран тоже нужно в VBlank, иначе будет чёрный экран до конца кадра с момента откл. экрана.

--- Цитата: Howard Phillips от 01 Апрель 2023, 00:13:15 ---Под нулевым байтом я подразумевал нулевой байт палитр, неудачно сформулировал.
--- Конец цитаты ---
Вы нормально написали, я тогда не понял, сам иногда ломаю голову когда нужно объяснить "индексы", "цвета", "индекс цвета", "индекс цвета в палитре" и т. п.

Навигация

[0] Главная страница сообщений

[#] Следующая страница

[*] Предыдущая страница

Перейти к полной версии