Ассемблер под Windows №22

OpenGL и структура команд процессора Intel


Доброго времени суток, уважаемые подписчики.


Один из наших проектов игра. Так как большинство выбравших игру интересует трёхмерное программирование, начнём именно с этого. Писать 3D среду самому долго и не совсем разумно. Direct3D из-за его com структуры является достаточно сложным в понимании (а также программировании на ассемблере), поэтому я выбрал OpenGL для нашего проекта. Сразу хочу сказать, что писать мы будем не игру определённого жанра, а 3D движок, на котором можно будет написать игру любого жанра. Для использования функций OpenGL в ассемблере Вам понадобятся следующие компоненты:
- файлы-дополнения OpenGL
- Структуры и константы OpenGL
- библиотеки импорта OpenGL
Создание OpenGL окна: OpenGL для сообщение с внешним миром использует так называемые Rendering Device Contexts (rDC), которые, в свою очередь, могут присвоены hDC окна windows. OpenGL создаёт виртуальную hDC на которую и происходит выдача изображения OpenGL, поэтому чтобы что-либо увидеть нам понадобится постоянно вызывать SwapBuffers. Достаточно важный момент это изменение формата пикселей окна, в котором мы хотим увидеть OpenGL изображение. Формат окна можно изменять лишь один раз за всю жизнь окна, поэтому чтоб повторно изменить этот формат надо убить текущее окно, создать такое же и уже ему установить нужный формат!
Для начала попытаемся сделать обычное OpenGL приложение без каких либо объектов. Посмотрим, что мы должны сделать:
- Создать окно
- Убедиться, что формат пикселей нам подходит
- Создать контекст OpenGL
- Связать его с нашим окном
- обеспечить постоянное обновление экрана (независимо от процедуры окна!)
- при выходе закрыть контекст OpenGL
Думаю, создание окна уже не является большой проблемой, поэтому сразу рассмотрим переключение формата пикселей. Формат пикселей окна может быть описан структурой PIXELFORMATDESCRIPTOR, которая выглядит следующим образом:

PIXELFORMATDESCRIPTOR STRUCT
 nSize           dw ?
 nVersion        dw ?
 dwFlags         dd ?
 iPixelType      db ?
 cColorBits      db ?
 cRedBits        db ?
 cRedShift       db ?
 cGreenBits      db ?
 cGreenShift     db ?
 cBlueBits       db ?
 cBlueShift      db ?
 cAlphaBits      db ?
 cAlphaShift     db ?
 cAccumBits      db ?
 cAccumRedBits   db ?
 cAccumGreenBits db ?
 cAccumBlueBits  db ?
 cAccumAlphaBits db ?
 cDepthBits      db ?
 cStencilBits    db ?
 cAuxBuffers     db ?
 iLayerType      db ?
 bReserved       db ?
 dwLayerMask     dd ?
 dwVisibleMask   dd ?
 dwDamageMask    dd ?
PIXELFORMATDESCRIPTOR ENDS

На первый взгляд структура кажется чем-то огромным и не понятным, но спешу вас заверить, понимать её полностью нам не понадобится, нам понадобятся лишь те пункты, которые знакомы почти каждому. После заполнения структуры передаём её адрес вместе с handle нашего окна. Выполнение следующих задач, создать контекст OpenGL и связать его с нашим окном, полностью ложиться на библиотеку OpenGL, нам же достаточно вызвать нужные функции и проверить результат на ошибки. Следующим шагом нам нужно обеспечить независимый цикл, обновляющий содержание нашего окна. Логично было бы создать отдельный тред, но мы поступим немного по-другому. У нас есть цикл, который принимает сообщения окна и передаёт их ему, так почему же нельзя параллельно обновлять содержимое окна? При этом разумнее использовать PeekMessage вместо GetMessage, так как вторая возвращает управление только если получает сообщение окну, а PeekMessage возвращает управление в любом случае ( нулевое значение в случае отсутствия сообщений). Закрытие контекста производиться функциями OpenGL. Но достаточно теории, посмотрим как всё это выглядит на практике:

include kernel32.inc
include user32.inc
include gdi32.inc
include opengl32.inc
include glu32.inc
include def32.inc
include macros.inc
include opengldef.inc
 .386
 .model flat
 .data
ClearDepth  dq 1.0f
fovy        dq 45.0f
zFar        dq 100.0f
zNear       dq 0.1f
aspect      dq ?
alpha       dd 0.5f

hWnd        dd ?
hDC         dd ?
glhDC       dd ?
PixelFormat dd ?
Width_      dd 300
Height_     dd 200

wc   WNDCLASSEX <size wc,cs_hredraw or cs_vredraw or CS_OWNDC,offset WndProc,0,0,?,?,?,0,0,offset ClassName,0>
pxl  PIXELFORMATDESCRIPTOR <028h,1,PFD_DOUBLEBUFFER or PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0>
msg_ MSG <?>

ClassName db 'OpenGL',0
 .code
_start:
 null ebx
 run Getmodulehandle, ebx
 mov wc.hInstance,eax
 mov esi,eax
 run LoadIcon, ebx,IDI_APPLICATION
 mov wc.hIcon,eax
 run LoadCursor, ebx, IDC_ARROW
 mov wc.hCursor,eax
 run RegisterClassEx, offset wc
 mov ecx,CW_USEDEFAULT
;Из-за большого кол-ва параметров я решил записать обычным способом
 push ebx
 push esi
 push ebx
 push ebx
 push Height_
 push Width_
 push ecx
 push ecx
 push WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
 push offset ClassName
 push offset ClassName
 push WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
 run CreateWindowEx
 mov hWnd,eax
 mov esi,eax
 run GetDC, eax
 mov hDC,eax
 run ChoosePixelFormat, eax, offset pxl
 mov PixelFormat,eax
 run SetPixelFormat, hDC, eax, offset pxl
 run wglCreateContext, hDC
 mov glHDC,eax
 run wglMakeCurrent, hDC, eax
 run ShowWindow, esi, SW_SHOWNORMAL
 run SetForegroundWindow, esi
 run SetFocus, esi
 run ReSizeGLScene ;Процедура, сообщающая OpenGL размеры окна
 run InitGL ;Включение нужных нам функций OpenGL
 mov edi,offset msg_
main_loop:
 run PeekMessage, edi, ebx, ebx, ebx, PM_REMOVE
 cmp msg_.message,WM_QUIT
 jz ProgramEnd
 test eax,eax
 jz glReNew
 run TranslateMessage, edi
 run DispatchMessage, edi
 jmp short main_loop
glReNew:
 run DrawGLScene ;Перерисовка сцены внутри OpenGL
 run SwapBuffers, HDC ;Вывод картинки в окно
 jmp short main_loop
ProgramEnd:
 run KillGLWindow ;Деинициализация OpenGL
 run ExitProcess, ebx

WndProc proc
 push ebp
 mov ebp,esp
 pusha
 null ebx
 mov edi,[ebp+08h]
 mov eax,[ebp+0ch]
 cmp eax,WM_DESTROY
 je close_proc
 cmp eax,WM_SYSCOMMAND
 je syscommand_proc
 cmp eax,WM_KEYDOWN
 je key_proc
Pass_Message:
 popa
 leave
 jmp DefWindowProc
syscommand_proc:
 mov eax,[ebp+10h]
 cmp eax,SC_SCREENSAVE
 je endWndProc
 cmp eax,SC_MONITORPOWER
 je endWndProc
 jmp short Pass_Message
key_proc:
 cmp dword ptr [ebp+10h], VK_ESCAPE
 jne EndWndProc
close_proc:
 run PostQuitMessage, ebx
EndWndProc:
 popa
 leave
 ret 16
WndProc endp

ReSizeGLScene proc
 cmp height_,ebx
 jne h_ok
 inc height_
h_ok:
 run glViewPort, ebx, ebx, width_, height_
 run glMatrixMode, GL_PROJECTION
 run glLoadIdentity
 fild width_
 fild height_
 fdivp st(1),st(0)
 fstp aspect
 pushq zFar
 pushq zNear
 pushq aspect
 pushq fovy
 run gluPerspective
 run glMatrixMode, GL_MODELVIEW
 run glLoadIdentity
 ret
ReSizeGLScene endp

KillGLWindow proc
 run wglMakeCurrent, ebx, ebx
 run wglDeleteContext, glhDC
 run ReleaseDC, hWnd, hDC
 run DestroyWindow, hWnd
 ret
KillGLWindow endp

InitGL proc
 run glShadeModel, GL_SMOOTH
 run glClearColor, ebx, ebx, ebx, alpha
 pushq ClearDepth
 run glClearDepth
 run glEnable, GL_DEPTH_TEST
 run glDepthFunc, GL_LEQUAL
 run glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
 ret
InitGL endp

DrawGLScene proc
 run glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
 run glLoadIdentity
 ret
DrawGLScene endp
 end _start

К сожалению, подробно рассматривать функции OpenGL мы не будем, так как для достижения нашей цели нам будет достаточно знать многие процедуры очень поверхностно (к тому же их название в большинстве случаев говорит само за себя). Для тех кто хочет подробнее заниматься OpenGL могу посоветовать книгу Дейва Шрайнера "OpenGL - официальный справочник" или ранее мной упомянутый opengl.hlp


Другим нашим проектом является лидер голосования, дизассемблер. К сожалению в этом случае нет никаких вступлений или заранее кем-то придуманных и написанных функций, поэтому начинать надо с самого сложного. Думаю многие уверены, что дизассебляция - процесс очень лёгкий, берём таблицу имён и по коду команды находим её имя... К сожалению, всё не так просто, многие команды процессора имею несколько кодов, причём некоторые одинаковы с другими командами (три бита в поля в байте операндов различают их). Поэтому начнём мы с корней всех наших будущих проблем, с структуры команды Intel совместимого процессора!
Команды бывают разной длинны, причём длинна их неограниченна и с появлением всё новых инструкций растёт и длинна команды. Все команды объединяет лишь то, что минимум первые 4 бита являются статической командой (возможно я очень не прав, но статической командой я называю ту часть команды, которая при любых параметрах остаются та же). Перед тем как мы рассмотрим структуры и примеры команд давайте заранее договоримся о названиях полей команд и их битах: - Код = несколько байт (или бит) статической команды. - ModR/M = 1 байт, описывающий операнды
- SIB = 1 байт, 32 битное расширение адресации
- w = бит, принимающий значение 0, ели команда работает с байтом, и 1, если с (двойным)словом
- s = бит, при помещения константы в 16/32 битный регистр. Если 0, то константа указана полностью, 1, если указан лишь младший байт.
- d = бит, указывающий направление действия
- reg = выбор одного из восьми регистров (см. ниже)
reg8 бит16 бит32 битаFPUmmxsse
000alaxeaxst(0)mm0xmm0
001clcxecxst(1)mm1xmm1
010dldxedxst(2)mm2xmm2
011blbxebxst(3)mm3xmm3
100ahspespst(4)mm4xmm4
101chbpebpst(5)mm5xmm5
110dlsiesist(6)mm6xmm6
111bhdiedist(7)mm7xmm7
- cond = условие действия (см. ниже) (для команд j**, cmov**, set**, fcmov**)
0000o
0001no
0010c/b/nae
0011nc/nb/ae
0100e/z
0101ne/nz
0110be/na
0111nbe/a
1000s
1001ns
1010p/pe
1011np/po
1100l/nge
1101nl/ge
1110le/ng
1111lne/g
- im = константа
- i8/i16/i32 = константа заданного размера
- ac = eax,ax,al
- r = регистр любого размера
- r8/r16/r32 = регистр указанного размера
- sr = сегментный регистр
- m = операнд в памяти
- mm = mmx
- xmm = sse
- st0 = st(0)
- sti = st(i)
Сегодня мы рассмотрим три самых простых, однобайтных типа команд. Первый встречается достаточно часто, это команды изменения флагов, коррекции чисал посе различных действий, то есть те команды, которые не имеют параметров, они содержал лишь 8 байтный код себя. В большинстве случаев они работают с регистром флагов или ac. Декодирование таких команд можно производить командой xlat, которая также является однобайтной, безоперандной командой.
Другой однобайтный вид команд выглядит следующим образом:
код команды(5 бит)reg(3 бита)
Обычно это команды, выполняющие ни от чего не зависящее действие с одним из регистров, примерами таких команд являются inc (40h or reg), dec (48 or reg), pop (58h or reg). Особым случаем является команда xchg, если один из операндов eax, то команда занимает 1 байт (90h or reg), если нет, то 2 (86h or W; reg shl 3). В следующем уроке составим полную таблицу возможных команд...


Всем, кто программирует на ассемблере, C(++) или Delphi советую заглянуть сюда


На сегодня это всё. Если есть какие вопросы, пишите. Пишите на Dark_Lord@RusFAQ.ru или Dark_Lord@land.ru.
Или свяжитесь со мной по ICQ, мой номер 126222874!

Сайт управляется системой uCoz