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

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

<< < (3/3)

Howard Phillips:
Спасибо, сделал прерывания NMI активными во время выключения экрана, и "рывок" фон пропал, все четко.
Еще добавил ожидание NMI перед отключением экрана вот так:

--- Код: ---void All_Off (void) {
    while (NMindex_flag == 0); // wait till NMI
NMindex_flag = 0;
PPU_MASK = 0x00;
}
void All_On (void) {
    while (NMindex_flag == 0); // wait till NMI
NMindex_flag = 0;
PPU_MASK = b0001_1110;
}

--- Конец кода ---
Но во время отключения экрана видно, что экран успевает "моргнуть", т.е. почернеть на один кадр, а потом картинка возвращается. Может у меня не успевает отработать код за один кадр?
ПС: И я нашел хороший способ узнать, как проверить сколько я потратил ROM. Можно открыть ROM  в редакторе тайлов YY-CHR и посмотреть сколько пустых страниц в файле. Очень просто и наглядно.

Sharpnull:

--- Цитата: Howard Phillips от 16 Апрель 2023, 22:12:28 ---Но во время отключения экрана видно, что экран успевает "моргнуть", т.е. почернеть на один кадр, а потом картинка возвращается.
--- Конец цитаты ---
Здесь мне не понятно что происходит, что вызывается и когда, как я понял: вызывается All_Off(), следующий кадр чёрный, след. кадр отображается как будто не было откл. экрана, а после кадры чёрные до вкл. экрана. Может в NMI происходит изменение PPUMASK, но тогда где снова PPUMASK = 0. Ожидание VBlank у вас тоже странное, ожидать пока NMindex_flag не станет != 0, но где установка NMindex_flag = 0 до вызова All_Off() и All_On(). Например, делают так:

--- Код: ---NMI:
  INC $xx
  RTI
WaitVBlank:
  LDA $xx
@loop:
  CMP $xx
  BEQ @loop
  RTS
--- Конец кода ---

Howard Phillips:
Вот пример вызова включения и выключения экрана:

--- Код: --- if (parameters_shown == false) {
            All_Off ();
            draw_all_parameters ();
            clear_status ();
            hide_weapon_submenu ();
            PPU_ADDRESS = 0x00;
            PPU_ADDRESS = 0x00;
            All_On ();
            parameters_shown = true;
        }

--- Конец кода ---
NMindex_flag изменяет в обработчике прерывания:

--- Код: ---void NMI (void) {
    ++NMindex_flag;
    ++Frame_Count;
    OAM_ADDRESS = 0;
    OAM_ADDRESS = 0;
    OAM_DMA     = 0x02; // push sprite data to OAM from $200-2ff
    // PPU_CTRL    = 0x90;
    // PPU_MASK    = temp_PPU_MASK; // screen on

// Сброс скрола
    SCROLL = 0x00;
    SCROLL = 0x00;
    // Выход из прерывания
    // Без него не получится реализовать корректный выход из прерывания
    asm ("rti");
}   

--- Конец кода ---
После вызова  All_Off() экран чернеет и  картинка возвращается только после вызова All_On().

Sharpnull:
Судя по коду, перед вызовом All_Off() у вас в NMindex_flag может быть любое значение от 0 до 255, тогда while (NMindex_flag == 0); будет в большинстве случаев не ждать и ждать до VBlank только с NMindex_flag == 0 (UPD) перед вызовом All_Off(). Почему у вас работает All_On() тоже не понятно, если только код срабатывает за один кадр между All_Off() и All_On(). У вас есть Frame_Count, вы можете ожидать как я описывал, на Си наверно так (лучше на ASM):

--- Код: ---int prev_Frame_Count = Frame_Count; // Либо во временную переменную
while (Frame_Count == prev_Frame_Count);
--- Конец кода ---

--- Цитата: Howard Phillips от 16 Апрель 2023, 23:15:50 ---После вызова  All_Off() экран чернеет и  картинка возвращается только после вызова All_On().
--- Конец цитаты ---
Но вы писали про моргание.

Howard Phillips:
Да, вы правы, у меня была странноватая логика ожидания прерывания. Это все остатки кода на основе которого я изучал разработку под NES на Си.
Переписал функцию ожидания прерывания на ассемблере вот так:

--- Код: ---; Ф-я ожидает прерывания NMI (конец кадра)
_Wait_Vblank:
lda $2002
bpl _Wait_Vblank
rts

--- Конец кода ---
bpl проверяет отрицательность  регистра, а в старшем бите у нас как раз бит который флагует о наличии прерывания.
Заменил все вызовы
while (NMindex_flag == 0); // wait till NMI
   NMindex_flag = 0;
на вызов функции:
 Wait_Vblank ();
Логика не сломалась, все работает, но "почернение" кадра не пропало.
Морганием я это называл, подразумевая, что происходит одна итерация моргания, как один раз закрыть глаза, а потом открыть.
Сейчас функции включения и выключения экрана выглядят так:

--- Код: ---void All_Off (void) {
    Wait_Vblank (); // wait till NMI

PPU_MASK = 0x00;
}


void All_On (void) {
    Wait_Vblank (); // wait till NMI

PPU_MASK = b0001_1110;
}

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

Sharpnull:

--- Цитата: Howard Phillips от 17 Апрель 2023, 00:30:13 ---Переписал функцию ожидания прерывания на ассемблере вот так:
--- Конец цитаты ---
Ожидать через lda $2002 bpl можно только с отключенным NMI, иначе может быть пропуск кадра. С вкл. NMI напишите _Wait_Vblank так:

--- Код: ---  LDA Frame_Count
@loop:
  CMP Frame_Count
  BEQ @loop
  RTS
--- Конец кода ---

--- Цитата: Howard Phillips от 17 Апрель 2023, 00:30:13 ---А вообще после отключения отображения спрайтов и фона, должны ли они оставаться на экране?
--- Конец цитаты ---
После PPUMASK = 0 будет весь фон одного цвета фона (у вас видимо чёрный). Если хотите "заморозить" картинку, то придётся без откл. экрана записывать в VBlank в течение нескольких кадров, но т. к. вы меняете фон, это будет заметно, остаётся делать двойную буферизацию: отображать один Nametable и изменять другой за пределами экрана, а потом в VBlank менять скрытый на отображаемый Nametable через Scroll ($2005) и Base nametable address ($2000), но вряд ли вы так будете делать.

Howard Phillips:
Заменил функцию ожидания на ваш код, но визуально разницу не заметил в работе игры. Но стабильность не помешает, я согласен.
А про борьбу с черным экраном я так и думал, что тут только или буферизация или фон редактировать маленькими кусочками за несколько срабатываний NMI. Самые критичные моменты моменты я потом перепишу без отключения экрана.
Сейчас все вроде бы работает как надо. Можно дальше двигаться к релизу.

Howard Phillips:
Здравствуйте. При разработке столкнулся с очередной проблемой.
Решил сделать раздельный скроллинг для вывода интерфейса вверху экрана (выставляю позицию камеры в 0, вывожу интерфейс, потом на при срабатывании флага нулевого спрайта возвращаю правильное положение камеры). Вот код:

--- Код: ---// Обработчик прерывания
void NMI (void) {
    ++Frame_Count;
    OAM_ADDRESS = 0;
    OAM_ADDRESS = 0;
    // Младший полубайт задает адрес RAM,
    // из которого будет заполняться OAM видеопамяти
    // В нашем случае копия ОАМ хранится по адресу $200-2ff
    // 0х02 - указывает адрес $200 из ОЗУ
    OAM_DMA     = 0x02;
    SCROLL = 0;
SCROLL = 0;
    // Выход из прерывания
    asm ("rti");

// Вот основной цикл тестовый
while (1) {
      // ЖДем конец кадра
        Frame_Count_old = Frame_Count;
    while (Frame_Count == Frame_Count_old);

// Меняем скроллинг
SCROLL = 0;
SCROLL = 0;
while ( (PPU_STATUS & b0100_0000) == 0);
// Обновление скроллинга
SCROLL = h_scroll;
SCROLL = 0;
                // Двигаем камеру
++X_hero;
if (X_hero > 127 && X_hero < 383) {
h_scroll = (unsigned char)(X_hero - 128);
//h_scroll += 5;
} else {
if (X_hero >= 383) {
h_scroll = 255;
}
}
}
}

--- Конец кода ---
Для срабатывания нулевого спрайта я в фон вывел цифру 0 и сверху наложил спрайт с красным патроном (его видно на скрине).
И так, в коде работает только обратчик прерывания, изменения позиции камеры и прокрутка камеры для проверки. Я убрал все лишнее для отладки.
Все работает, но не совсем верно. Текст интерфейса (количество патронов и т.д.) выводится через кадр, т.е. мерцает. Судя по всему каждый 2-й кадр интерфейс выводится в неправильное положение скроллинга и получается мерцание.
Еще есть у меня баг со скроллигом, когда движется камера, некоторые цвета заметно темнеют. Возможно это связанные проблемы.
Прикрепляю скрин на котором я поймал правильный вывод интерфейса. Четко видно, что вывод интерфейса работает через кадр. Я из кода все лишнее выкинул и непонятно, что может ломать скроллинг. Очень надеюсь на помощь, так как спрайтами меню выводить не хочется (они почти закончились).

Sharpnull:
Howard Phillips, бит Sprite 0 Hit очищается на "dot 1 of the pre-render line" (в Mesen это Scanline: -1, после 260), а выход из NMI происходит раньше (для NTSC начинается со scanline 241 и почти сразу завершается), поэтому этот цикл "while ( (PPU_STATUS & b0100_0000) == 0);" не выполняется, если перед NMI был Sprite 0 Hit. Попробуйте добавить перед этим циклом: while ( (PPU_STATUS & b0100_0000) != 0);.

Howard Phillips:

--- Цитата: Sharpnull от 11 Май 2024, 17:41:15 ---Howard Phillips, бит Sprite 0 Hit очищается на "dot 1 of the pre-render line" (в Mesen это Scanline: -1, после 260), а выход из NMI происходит раньше (для NTSC начинается со scanline 241 и почти сразу завершается), поэтому этот цикл "while ( (PPU_STATUS & b0100_0000) == 0);" не выполняется, если перед NMI был Sprite 0 Hit. Попробуйте добавить перед этим циклом: while ( (PPU_STATUS & b0100_0000) != 0);.

--- Конец цитаты ---
Sharpnull, вы гений, спасибо. Заработало с первого раза. Не перестаю удивляться подобным подводным камням при разработке под NES.

А нет идей по поводу потемнения некоторых цветов при изменении положения скроллинга? Или это возможно как-то пиксели смещаются немного и получается такой эффект из-за наложения. Полосы, которые нарисованы прозрачными пикселями становятся толще, но скринами это поймать не смог и некоторые цветные пиксели тоже как-будто тоже темнеют немного. Возможно как у меня скроллинг на 1 пиксель как-то дергается и смазываются цвета?
Скринами не смог поймать этот момент, что показать, видно только в динамике игры (на видео тоже почему-то не видно эффекта).

Sharpnull:

--- Цитата: Howard Phillips от 13 Май 2024, 05:25:57 ---Скринами не смог поймать этот момент, что показать, видно только в динамике игры (на видео тоже почему-то не видно эффекта).
--- Конец цитаты ---
Это дурацкие ЖК экраны, пиксели же не сразу меняют правильный цвет, но на видео должно быть видно во время воспроизведения. Свою матрицу можете протестировать https://www.testufo.com/ghosting. Запустите Ninja Gaiden, там сразу же забор при движении становится темнее из-за паттерна "2px тёмных, 2px серых". Может зависит не только от скорости матрицы, но и типа, у меня IPS в старом дешёвом мониторе.

Howard Phillips:
Sharpnull, Проверил на Ninja Gaiden свой монитор, действительно забор темнее при скроллинге становится на моем мониторе. Эффект похож. Потом надо проверить как это выглядит на ЭЛТ телеке и реальной консоли.

Навигация

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

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

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