Сборка Turbo C / VGA x86: копирование с оперативной памяти на vram - PullRequest
0 голосов
/ 14 сентября 2018

Мне просто нравится с Turbo C рисовать «спрайты» на 8086/286 (эмулированном с pcem) с картой MCGA / VGA.

Скомпилированный с Turbo C 3.0, он должен работать на реальных8086 с MCGA.Я не использую режим VGA x, потому что он немного сложен, и мне не нужен дополнительный vram для того, что я хочу сделать, даже если на экране есть мерцание, это нормально:).

В C у меня есть набор memcpys, перемещающий данные из загруженной структуры спрайта в VGA в режиме 13:

byte *VGA=(byte *)0xA0000000L;    
typedef struct tagSPRITE             
{
    word width;
    word height;
    byte *data;
} SPRITE;

void draw_sprite(SPRITE *sprite){
    int i = 0; int j = 0; 
    for(j=0;j<16;j++){
        memcpy(&VGA[0],&sprite->data[i],16);
        screen_offset+=320;
        i+=16;
    }
}

Цель состоит в том, чтобы преобразовать этот код в определенную функцию сборки, чтобы ускорить процесснемного.


(примечание редактора: это была первоначальная попытка asm и текст, на котором был основан ответ. Просмотрите историю изменений, чтобы увидеть, что случилось с этим вопросом. Все было удалено в последнем редактировании, делаятолько собственный ответ автора имеет смысл, поэтому это редактирование пытается сделать так, чтобы оба ответа имели смысл.)

Я пытался написать его в сборке с чем-то вроде этого, что, я уверен, имеет огромные ошибки:

void draw_sprite(SPRITE *sprite){
    asm{
        mov ax,0A000h
        mov es,ax           /* ES points to the video memory */

        mov di,0            /* ES + DI = destination video memory */
        mov si,[sprite.data]/* source memory ram ???*/
        mov cx,16           /* bytes to copy */

        rep movsb           /* move 16 bytes from ds:si to es:di (I think this is the same as memcpy)*/

        add di,320          /* next scanline in vram */         
        add si,16           /* next scanline of the sprite*/
        mov cx,16   

        rep movsb           /* memcpy */

        /*etc*/
    }
}

Я знаю, что адрес оперативной памяти не может быть сохранен в 16-битном регистре, потому что он больше, чем 64 КБ, поэтому mov si,[sprite.data] не будет работать.

Так как жеЯ передаю адрес оперативной памяти в регистр Си? (если это возможно).

Я знаю, что я должен использовать регистры ds и si, чтобы установить что-то вроде «банка» в «ds», а затем регистр «si» может читать 64-килобайтный фрагмент памяти оперативной памяти (так, чтобы movsbможно переместить ds: si в es: di).Но я просто не знаю, как это работает.

Мне также интересно, будет ли этот asm-код быстрее, чем код c (на 8086 МГц или 286), потому что вам не нужноповторяйте первую часть каждый цикл.

Я не копирую с vram на vram на данный момент, потому что мне пришлось бы использовать режим X, и это другая история.

Ответы [ 2 ]

0 голосов
/ 17 сентября 2018

Спасибо Майклу Петчу, Питеру Кордесу и всем остальным.Я получил ответ.

Код сборки для копирования данных в видеопамять vga выглядит следующим образом:

DGROUP          GROUP    _DATA, _BSS
_DATA           SEGMENT WORD PUBLIC 'DATA'
_DATA           ENDS
_BSS            SEGMENT   WORD PUBLIC 'BSS'             
_BSS            ENDS
_TEXT           SEGMENT BYTE PUBLIC 'CODE'
                ASSUME CS:_TEXT,DS:DGROUP,SS:DGROUP

            PUBLIC _draw_sprite       
_draw_sprite    proc    far 
    push bp
    mov bp,sp
    push ds
    push si
    push di
    ;-----------------------------------
    lds     bx,[bp+6]
    lds     si,ds:[bx+4]        ; sprite->data to ds:si
    mov     ax,0A000h
    mov     es,ax                       
    mov     di,0                ; VGA[0] to es:di

    mov     ax,16               ; 16 scan lines
copy_line:  
    mov     cx,8
    rep     movsw               ; copy 16 bytes from ds:si to es:di
    add     di,320-16           ; go to next line of the screen
    dec     ax
    jnz     copy_line
    ;-----------------------------------
    pop di
    pop si
    pop ds
    mov sp,bp
    pop bp
    ret 
_draw_sprite    endp

Объявите функцию в c как:

    void draw_sprite(SPRITE *spr);

Данные, хранящиеся в spr-> data, это массив чисел (от 0 до 255, в котором хранится цвет пикселя).

Этот код, наконец, рисует растровое изображение 16x16 в позиции x = 0, y = 0.

Большое спасибо!

0 голосов
/ 14 сентября 2018

rep movsb увеличивает SI и DI, а также уменьшает CX .Это как memcpy, который берет свои dst, src по ссылке и обновляет их до конца скопированной области.

Так что вам нужно add di, 320-16, а si уже указывает на следующую строку спрайта(поскольку шаг строки соответствует ширине = 16).

Что касается сегментации, movsb копирует с DS:SI до ES:DI, поэтому настройте ES: DI, чтобы указатьв видеопамяти правильная.

Соглашение о вызовах Turbo C требует / обеспечивает DF = 0 при входе / выходе функции (как обычные 32-разрядные соглашения о вызовах), поэтому вам не нужно cld, чтобы убедиться, чтоmovsb идет в правильном направлении (вперед, а не назад).(Если вы использовали std где-то еще и не положили его обратно, исправьте его там, чтобы избежать нарушения соглашения о вызовах.)

Соглашение о вызовах в Turbo C также имеет AX / BC / CX / DX с закрытым вызовоми ES.( Спасибо @ MichaelPetch ).Если его встроенный asm похож на MSVC, компилятор сохранит / восстановит DI и SI для вас.Но, возможно, он не сохраняет и не восстанавливает DS для вас, поэтому @ MichaelPetch предлагает вам нужно будет нажать / выдвинуть DS, чтобы сохранить / восстановить его самостоятельно.Посмотрите на asm, сгенерированный компилятором, чтобы убедиться, что вы соблюдаете соглашение о вызовах.

Из вашего обновленного вопроса мы можем видеть, что ваши варианты сборки включают модель памяти = большой, которая превращает все указатели в дальние указателиЭто будет значительное замедление по сравнению с ручным выбором, какие указатели должны быть FAR, а другие только 16-битными.Но если у вас нет причин изучать 16-битную сегментацию в реальном режиме и все, что больше не актуально, продолжайте использовать это.(Вы можете выбрать модель памяти, где, по крайней мере, код может быть рядом, поэтому рядом с call / ret можно только выдвигать / выдвигать значение IP, а не CS.)


Вы можете поместить код вцикл, как это.

У меня есть смесь жесткого / ширины / высоты кодирования и загрузки его, как ваш вопрос, но если вы вычислите шаг строки в BX (320-ширина), у вас есть достаточно регистроввывести расчеты.Сама ветвь цикла уже обрабатывает также спрайты с переменным временем выполнения.

    push  ds

    xor   di,di             // DI=0

    //mov   si,[sprite.data]  /* source memory ram ???*/
    lds   si,[sprite.data]  // with your build options, everything is a seg:off FAR pointer
    lea   ax, [si + 16*16]  // end_src pointer

    mov   dx, [sprite.width]
    shr   dx, 1              // words to copy = bytes / 2
    // if you can't assume even width, then just use movsb
    // or optimize with rep movsb + a test of the low bit for one movsb

@loop:                    // do {
    mov   cx,dx            /* words to copy */

    rep movsw             /* copy 16 bytes from ds:si to es:di */

    add   di, 320-16      /* starting column in next scanline in vram */         
    // add si, 0          // sprite row stride - width = 0

    cmp   si, ax
    jb   @loop           // } while(src < endsrc);

    pop   ds

Обратите внимание на использование movsw для копирования в 2-байтовых чанках.x86 до PPro действительно просто копировал 1 байт или 1 слово за раз, в зависимости от размера операнда.

PPro и более поздние версии имеют микрокод быстрых строк, который копирует большими кусками.Но это приводит к значительным накладным расходам при запуске, поэтому для 16-битного режима на современном x86 было бы лучше использовать, возможно, 4 целочисленных регистра DWORD (eax) или qword с x87 fild qword / fistp или 16-байт с одним регистром XMM.

На реальных 8086 или 286 fild / fistp будет ужасно медленным по сравнению с целочисленными копиями.С 16-битной шиной данных вы все равно можете копировать только 2 байта за раз, поэтому rep movsw хорошо для реальных 286.

См. Также Какую настройку выполняет REP?

И Улучшенный REP MOVSB ​​для memcpy для memcpy на современном x86 (хотя в основном и для больших копий.)

Также обратите внимание, что VRAM обычно не кэшируется или комбинируется при записи, поэтомуесли вы на самом деле оптимизируете процедуру копирования в VRAM, несколько узких хранилищ для одной и той же строки кэша отстой для UC, но неплохо для WC, на процессоре с кешем.

...