1. Пересадил на последнюю редакцию движка. 2. Заменил x87 на SSE, так понятнее и человеку, и машине (оставил только fsincos). 3. Развернул полигоны, чтобы не мерцали. 4. Добавил свет 5. Выставил камеру Мышкой всё двигается, как в CAD-системах: с зажатой левой кнопкой - поворот камеры, с зажатой правой кнопкой - поворот объекта, с зажатым колесом - перенос камеры, прокрутка колеса - наезд камеры, двойной щелчок колесом - исходное положение С клавиатуры: стрелки, PagUp, PageDown - движение камеры, WSADQE - движение объекта (разница заметна при поворотах после смещения, см. координаты на статус-баре), пробели первое нажатие Esc - сброс в исходное положение, второе нажатие Esc - выход, F1, Ctrl+W - акселераторы. Под Windows 11 (Core i3, NVIDIA 1650) всё нормально ATI Rage Mobility-M1 под Windows Me гонит пургу со скоростью 37 FPS: --- Сообщение объединено, 28 сен 2026 --- Вот такой движок на MASM 32. А парсера как не было, так и нет
На моём музейном экспонате процессора и Windows 10 Pro x64 - всё ок ! Скорость 325 FPS и действительно стало красивее и лучше ( подсветка и т.п. ). Вот что значит - профессиональный подход к делу! Вопрос с управлением поворота вокруг оси снимается - разобрался. Просто стало чуть по-другому - нужно правую кнопку мыши удерживать. Так что - всё ок!
Я пересветил, на самом деле. У света три компонента: рассеянное, диффузное и бликовое. Я сделал рассеянное и диффузное слишком ярким, а бликовое не делал. Сейчас исправил. Стало равномернее. Но почему ATI Rage Mobility-M1 ломает полигоны - не знаю. Надо разбираться. --- Сообщение объединено, 28 сен 2026 --- Так и не нашёл, в чём проблема у ATI Rage Mobility-M1. Но зато попутно сделал много улучшений, например единый источник света с тремя компонентами, а не три по одному в одном месте. И обогатил лог: --- Сообщение объединено, 28 сен 2026 --- Она у меня работает под Windows Me на драйвере версии 1.1.0, написанном под Windows 95. Ну хоть не бета.
А мой самый первый файл то же самое или работает ? --- Сообщение объединено, 28 сен 2026 --- Казалось бы, куда ещё лучше, но последний вариант действительно, самый лучший. Правда FPS чуть просел с 325 на 290, ну это по-моему, ерунда ( в смысле, не критично ).
Да, то же самое. ATI Rage Mobility-M1 у меня почему-то не пересекает плоскости. Почему - не пойму. Всё перепроверил. Вот самый простой пример - пересечение куба и пола: Буфер глубины даже не скрывает оси внутри куба. --- Сообщение объединено, 28 сен 2026 --- Разобрался. Надо было сделать так: Код (ASM): ;gluPerspective Arguments fovy real8 45.0 zNear real8 100.0 ;Было 1.0 zFar real8 100000.0 ;Было 1000000.0 Я закладывал универсальные значения (от миллиметра до километра) На старой видеокарте z-буфер не справлялся. А сейчас мой старичок выдаёт вот такую красоту! GRAFik, спасибо за алгоритм!
Движок под х32 готов! Вот они, возможности ассемблера - исполняемый файл размером 17 килобайт: Регистрация класса окна и цикл сообщений Меню с акселераторами Полная инициализация OpenGL-контекста с выбором пиксельного формата Алгоритмическая сборка геометрии по заданной таблице элементов Конвейерная отрисовка массивов glDrawArrays 3D-навигация с помощью клавиатуры и мыши SSE-процедуры умножения и поворота матриц камеры и объекта Динамически обновляемый статус-бар из шести частей Конвертер из IEEE-754 в десятичное ACSII-представление Счётчик тактов и FPS Логгер с форматированием десятичных чисел Диалоги About и Close, разбор кодов ошибок Свобода от CRT и сторонних библиотек. И всё это запускается на процессоре Intel 2000 года: Движок MASM32 - это не порт ранее опубликованного 64-битного движка. Это самостоятельная программа - кстати, более грамотно построенная, т.к.: использует раздельные матрицы проекции, камеры и объекта, рисует массивами с помощью конвейера glDrawArrays, а не отдельными вызовами glBegin/glEnd
ml64, так теперь нужно сделать такую же грамотно построенную 64-х битную версию, с учётом всех спецификаций win. 64 ABI и т.д. и т.п. Если, конечно, у вас найдётся время для этого. Я бы даже мог тоже в этом поучавствовать, если бы у вас, допустим, была бы проблема со временем, для выяснения каких-нибудь деталей и тонкостей, связаных с той же 64-х битной спецификацией.
Да, и я её уже делаю. Вы тоже можете параллельно механически перевести Вашу анимированную программу с х32 на х64. Это совсем не рутина, а по-настоящему творческая работа. Там много интересного, например: а) использование новых инструкций, например, haddps, которой очень не хватало в SSE1, б) использование неразрушающих инструкций, которые экономят регистры, в) переупаковка из xmm-регистров в ymm-регистры, г) замена локальных переменных на регистры, т.к. их в 2 раза больше на х64 - следовательно, мы меньше обращаемся к памяти, а это критически важно в циклах и при пересчёте матриц. Что касается меня, то мне интересна история OpenGL (что я делаю сейчас): ОС3264OpenGL1.04.0КонвейерglDrawArraysVBOДоступAPI, display listsШейдерыТ.е. я готовлю движок OpenGL-64 к восприятию шейдеров, чтобы туда можно было загрузить "Москвич" и вообще всё, что угодно, в формате .obj. --- Сообщение объединено, 29 сен 2026 --- К вопросу о display lists Не знаю, как перевести - дословно: "списки отображения", по сути - прекомпилированные функции, т.е. своеобразный кэш В дисплей-листах можно прекомпилировать непосредственные обращения glBegin/glEnd, но использование их с glDrawArrays запрещено спецификацией, т.е. это не наш случай. С методических целях я запёк установки света в дисплей-лист, но ощутимого прироста FPS это не дало. Спойлер: Дисплей-лист Код (ASM): BuildLightingDisplayList proc push 4864h ;GL_COMPILE push LIST_LIGHTING call glNewList ;2.1. Setup Lighting invoke glEnable,0B50h ;GL_LIGHTING invoke glEnable,4000h ;GL_LIGHT0 push offset lightAmbient push 1200h ;GL_AMBIENT push 4000h ;GL_LIGHT0 call glLightfv push offset lightDiffuse push 1201h ;GL_DIFFUSE push 4000h ;GL_LIGHT0 call glLightfv push offset lightSpecular push 1202h ;GL_SPECULAR push 4000h ;GL_LIGHT0 call glLightfv ;2.2. Set Materials to Lighting push 1602h ;GL_AMBIENT_AND_DIFFUSE push 408h ;GL_FRONT_AND_BACK call glColorMaterial push 0B57h ;GL_COLOR_MATERIAL call glEnable push offset lightSpecular push 1202h ;GL_SPECULAR push 0408h ;GL_FRONT_AND_BACK call glMaterialfv push 3f000000h ;0.5 push 1601h ;GL_SHININESS push 0408h ;GL_FRONT_AND_BACK call glMaterialf call glEndList ret BuildLightingDisplayList endp Спойлер: Вызов дисплей-листа Код (ASM): ;Compute Projection, Camera and Object Matrices call GetDefaults ;Pre-compile the Display List: ;3.1. Setup Lighting ;3.2. Set Materials to Lighting call BuildLightingDisplayList ;Apply Defaults call ResetScene Сборка во вложении.
В процессе перевода с х32 на х64 понаходил ошибок. Из-за ошибки переполнения полностью переписал конвертер float-ANSI. Теперь х87 (80-битный fbstp) не используется. Только АЛУ, только хардкор. Всё как в ламповых учебниках: IEEE-754, знак, характеристика, мантисса. Теперь нули, бесконечности и нечисла обрабатываются отдельно. Ибо нефиг.
Так, 64-битная версия есть, но пока что это не более чем перевод. Эта версия использует 64-битные РОН, но не оптимизирована под AVX и рисует с помощью DrawArrays (OpenGL 1.1), а не VBO. Что касается 32-битных версий, то там всё намного интереснее. Когда я писал x32, я мыслил в парадигме stdcall, поэтому всё передавал через стек. Когда я перешёл к x64 и fastcall, то многое переработал и в 32-битных версиях. Например, обработчик клавиатуры у меня теперь забирает аргументы прямо из регистров, а не через стек: Код (ASM): ;Up Arrow | World moves Backward | Local +z axis cmp byte ptr[key+26h],0 je @f movss xmm0,f32_posOne ;xmm0 = direction (either +1.0 or -1.0) call CameraWalkMagnitude mov ecx,2 ;2 is for Local z axis call CameraMove ;ecx = axis, xmm0 = magnitude В общем, 32-битные версии стали чище, проще и быстрее.
Свежий подгон. Обновил алгоритм пересчёта локальных координат в мировые. Теперь используется метод Крамера - тот самый, который проходят в 1 семестре 1 курса: . Вот так он выглядит на SSE1 (для процессоров с xmm0..xmm7): Спойлер: GetGlobalOrigin Код (ASM): GetGlobalOrigin proc ;ecx = pMtxLocal:DWORD ;edx = pVecGlobal:DWORD ;Fastcall: ;ecx = pMtxLocal ;edx = pVecGlobal ;XMM Comment Order: little-endian ;p_local = A * p_world + t ;p_world = -A(-1) * t ;p0 = det(A0) / det(A) ;p1 = det(A1) / det(A) ;p2 = det(A2) / det(A) ;A = ;[m00 m04 m08] ;[m01 m05 m09] ;[m02 m06 m10] movaps xmm0,oword ptr[ecx+00*4] ;[m03 m02 m01 m00] movaps xmm1,oword ptr[ecx+04*4] ;[m07 m06 m05 m04] movaps xmm2,oword ptr[ecx+08*4] ;[m11 m10 m09 m08] ;det(A) ;= m00*(m05*m10 - m06*m09) ;- m04*(m01*m10 - m02*m09) ;+ m08*(m01*m06 - m02*m05) call Determinant movss xmm3,xmm0 ;xmm3 = detA ;t = (m12, m13, m14) movaps xmm5,oword ptr[ecx+12*4] ;[m15 m14 m13 m12] ;b = -t = (-m12, -m13, -m14) xorps xmm4,xmm4 subps xmm4,xmm5 ;xmm4 = b ;A0 = A with column 0 replaced by b ;[b0 m04 m08] ;[b1 m05 m09] ;[b2 m06 m10] movaps xmm0,xmm4 ;[b3 b2 b1 b0] movaps xmm1,oword ptr[ecx+04*4] ;[m07 m06 m05 m04] movaps xmm2,oword ptr[ecx+08*4] ;[m11 m10 m09 m08] ;det(A0) ;= b0 *(m05*m10 - m06*m09) ;- m04*(b1*m10 - b2*m09) ;+ m08*(b1*m06 - b2*m05) call Determinant ;p0 = det(A0) / det(A) divss xmm0,xmm3 ;xmm0 = detA0 / detA movss dword ptr[edx+00],xmm0 ;A1 = A with column 1 replaced by b ;[m00 b0 m08] ;[m01 b1 m09] ;[m02 b2 m10] movaps xmm0,oword ptr[ecx+00*4] ;[m03 m02 m01 m00] movaps xmm1,xmm4 ;[b3 b2 b1 b0] movaps xmm2,oword ptr[ecx+08*4] ;[m11 m10 m09 m08] ;det(A1) ;= m00*(b1*m10 - m09*b2) ;- b0* (m01*m10 - m09*m02) ;+ m08*(m01*b2 - b1*m02) call Determinant ;p1 = det(A1) / det(A) divss xmm0,xmm3 ;xmm0 = detA1 / detA movss dword ptr[edx+04],xmm0 ;A2 = A with column 2 replaced by b ;[m00 m04 b0] ;[m01 m05 b1] ;[m02 m06 b2] movaps xmm0,oword ptr[ecx+00*4] ;[m03 m02 m01 m00] movaps xmm1,oword ptr[ecx+04*4] ;[m07 m06 m05 m04] movaps xmm2,xmm4 ;[b3 b2 b1 b0] ;det(A2) ;= m00*(m05*b2 - b1*m06) ;- m04*(m01*b2 - b1*m02) ;+ b0* (m01*m06 - m05*m02) call Determinant ;p2 = det(A2) / det(A) divss xmm0,xmm3 ;xmm0 = detA2 / detA movss dword ptr[edx+08],xmm0 ret GetGlobalOrigin endp Определитель: Спойлер: Determinant Код (ASM): Determinant proc ;Fastcall: ;xmm0 = [a03 a02 a01 a00] ;xmm1 = [a13 a12 a11 a10] ;xmm2 = [a23 a22 a21 a20] ;Preserved ;xmm3 = [??? ??? ??? detA] ;xmm4 = [??? b2 b1 b0] ;Accessible ;xmm5..xmm7 ;Output: ;xmm0 = [??? ??? ??? det] ;XMM Comment Order: little-endian ;det ;= a00*(a11*a22 - a12*a21) ;+ a01*(a12*a20 - a10*a22) ;+ a02*(a10*a21 - a11*a20) ;A = [a13, a10, a12, a11] movaps xmm5,xmm1 shufps xmm5,xmm5,11001001b ;B = [a23, a21, a20, a22] movaps xmm6,xmm2 shufps xmm6,xmm6,11010010b ;A*B = [a13*a23, a10*a21, a12*a20, a11*a22] mulps xmm5,xmm6 ;C = [a13, a11, a10, a12] movaps xmm6,xmm1 shufps xmm6,xmm6,11010010b ;D = [a23, a20, a22, a21] movaps xmm7,xmm2 shufps xmm7,xmm7,11001001b ;C*D = [a13*a23, a11*a20, a10*a22, a12*a21] mulps xmm6,xmm7 ;[0, cz, cy, cx] subps xmm5,xmm6 ;det = dot(col0, cross) ;[0, a02*cz, a01*cy, a00*cx] mulps xmm0, xmm5 ;Horizontal sum of lanes 0..2 movaps xmm6,xmm0 shufps xmm6,xmm6,01001110b ;Rotate Right by 2 Lanes addps xmm0,xmm6 movaps xmm6,xmm0 shufps xmm6,xmm6,10110001b ;Swap Even and Odd Lanes addss xmm0,xmm6 ;result in xmm0[0] ret Determinant endp И за это машина награждает: 600 FPS на Intel Iris Xe 2023 года (Gen 11):
Ну чисто субъективно мне нравится тот вариант, где оси вращения взаимно перпендикулярны. Но можно угол поворота/радиус/скорость сделать настраиваемыми (клавиши +/-, стрелочки, PgUp/PgDn, колесо мышки). А ещё - что мне нравится в компиляторах - выбор набора инструкций по cpuid: Код (ASM): 00007FF6CD492720 | 0FA2 | cpuid | 00007FF6CD492722 | 81F1 6E74656C | xor ecx,6C65746E | 00007FF6CD492728 | 81F2 696E6549 | xor edx,49656E69 | 00007FF6CD49272E | 0BD1 | or edx,ecx | 00007FF6CD492730 | 8BE8 | mov ebp,eax | 00007FF6CD492732 | B8 01000000 | mov eax,1 | 00007FF6CD492737 | 81F3 47656E75 | xor ebx,756E6547 | 00007FF6CD49273D | 0BD3 | or edx,ebx | 00007FF6CD49273F | 8D48 FF | lea ecx,qword ptr ds:[rax-1] | 00007FF6CD492742 | 0FA2 | cpuid | 00007FF6CD492744 | 8BF9 | mov edi,ecx | 00007FF6CD492746 | 75 5E | jne 2_voxel64demo.7FF6CD4927A6 | 00007FF6CD492748 | 25 F03FFF0F | and eax,FFF3FF0 | 00007FF6CD49274D | 48:C705 90690100 0080 | mov qword ptr ds:[7FF6CD4A90E8],8000 | 00007FF6CD492758 | 48:C705 8D690100 FFFF | mov qword ptr ds:[7FF6CD4A90F0],FFFFFFFFFFFF | 00007FF6CD492763 | 3D C0060100 | cmp eax,106C0 | 00007FF6CD492768 | 74 28 | je 2_voxel64demo.7FF6CD492792 | 00007FF6CD49276A | 3D 60060200 | cmp eax,20660 | 00007FF6CD49276F | 74 21 | je 2_voxel64demo.7FF6CD492792 | 00007FF6CD492771 | 3D 70060200 | cmp eax,20670 | 00007FF6CD492776 | 74 1A | je 2_voxel64demo.7FF6CD492792 | 00007FF6CD492778 | 05 B0F9FCFF | add eax,FFFCF9B0 | 00007FF6CD49277D | 83F8 20 | cmp eax,20 | 20:' ' 00007FF6CD492780 | 77 24 | ja 2_voxel64demo.7FF6CD4927A6 | 00007FF6CD492782 | 48:B9 010001000100000 | mov rcx,100010001 | 00007FF6CD49278C | 48:0FA3C1 | bt rcx,rax | rax:EntryPoint 00007FF6CD492790 | 73 14 | jae 2_voxel64demo.7FF6CD4927A6 | 00007FF6CD492792 | 44:8B05 9F790100 | mov r8d,dword ptr ds:[7FF6CD4AA138] | 00007FF6CD492799 | 41:83C8 01 | or r8d,1 | 00007FF6CD49279D | 44:8905 94790100 | mov dword ptr ds:[7FF6CD4AA138],r8d | 00007FF6CD4927A4 | EB 07 | jmp 2_voxel64demo.7FF6CD4927AD |
ml64, Еще робот нашел. Код (Text): # Краткий отчёт по ошибкам ## Критичные (ломают работу) | # | Место | Суть | |---|-------|------| | 1 | `ScaleFraction`, pre-shift | `shr ebx,cl` при `cl ≥ 32`: `cl` маскируется до 5 бит (`cl & 31`), сдвиг неверный. Диапазон `ecx` до 118. Нужен каскадный сдвиг с ограничением `cl ≤ 31`. | | 2 | `mov pszOutput,rdi` (в начале) | Перезапись параметра **сдвинутым** указателем. Затем `Exp0`/`Exp255`/`Overflow` читают `pszOutput` и получают `buf+1` вместо `buf`. Минус не стирается. | | 3 | `szOverflow` | Не определена в `.data` (в комментариях только `szZero`, `szNaN`, `szInfinity`). Ошибка компоновки. | | 4 | `Infinity` | Не сбрасывает `rdi` на начало буфера. Для `-Inf` получается `"-Inf"`, для `-NaN` — `"NaN"`. Несогласованно. | ## Средние (корректность) | # | Место | Суть | |---|-------|------| | 5 | Округление дроби | `add eax,5; div 10` — half-up. `printf` использует half-even. Расхождение на граничных значениях. | | 6 | `-0.0` | `Exp0` стирает минус. `printf` даёт `-0.0000`. | | 7 | Соглашение вызова | Параметры объявлены как `proc binFloatValue:DWORD, pszOutput:QWORD`, но комментарии говорят про `esi`/`edi`. В 64-битном MASM параметры в `rcx/rdx`. Проверить `PROLOG`. | ## Мелкие (стиль/скорость) | # | Место | Суть | |---|-------|------| | 8 | `mov binFloatValue,esi` | Бесполезная (и опасная) запись в параметр. | | 9 | `r10` (sign) | Устанавливается, нигде не читается — мёртвый код. | | 10 | `loop` в `CopyLoop`/`CopyFrac` | Медленно (~5 тактов), лучше `dec ecx / jnz`. | | 11 | `div ebx` (ebx=10) | ~20–40 тактов, заменить умножением на магию `0xCCCCCCCD`. | | 12 | `lea r15, intBuf` / `fracBuf` | Зависит от корректности `PROLOG 100h` + `LOCAL`. Проверить. | ## Что проверено и НЕ является ошибкой - `mul edx` (100000): макс ~1.7e12 < 2^64 — ок. - `shrd eax,edx,cl` в `DoScale64`: результат < 100000 < 2^32 — ок. - `inc r11d` при carry из дроби: `intPart` не может быть `0xFFFFFFFF` — ок. - `shld edx,eax,cl` в `ShiftLeft`: `cl < 32` гарантировано — ок. - `LoopInt`/`LoopFrac`: `ebx=10` всегда установлен — ок. - `r15` в буферах: `intBuf[16]` (≤10 цифр), `fracBuf[8]` (ровно 4) — влезает. ## Приоритет исправлений 1. Убрать `mov binFloatValue,esi` и `mov pszOutput,rdi` (или поменять направление). 2. Починить pre-shift в `ScaleFraction`. 3. Определить `szOverflow`. 4. Решить вопрос знака для `-0.0`/`-NaN`/`-Inf`. 5. Заменить `div` на магию, `loop` — на `dec/jnz`. 6. Перейти на half-even, если нужна совместимость с `printf`.