Если называть ЯВУ все что не является ассемблером(например си): на ЯВУ есть свой кайф при программировании. Например если твой код не зависит от внешних библиотек/фреймворков это тоже кайф.
А вот тут Intel был ни при чём. Это был типичный ассемблерный ночной баг. Нашёл, исправил. Мне одному не дают спать Вулкан и ассемблер по ночам?
Нет я тоже по всем ночам не сплю - всё жду когда вы мне, под мой музейный экспонат процессора, код на MASM64 - для OpenGL напишите.
Раз обещал - нехорошо заставлять ждать. Вообще я хотел сначала закончить парсер obj -> VBO, а потом перенести его в OpenGL-рамку, но если Вас терзает бессонница, то придётся отвлечься
Здесь наши интересы совпадают: мой движок OpenGL основан на технологиях прошлого века (OpenGL 1.0). Работать с реальными 3D-моделями он просто не способен. Поэтому мне нужно прикрутить к нему парсер VAO/VBO/EBO. Если при этом он кому-то будет полезен, то я только буду рад. Vulkan - штука нестабильная и требует наличия библиотеки. OpenGL под Windows запускается из коробки. У самурая нет цели, есть только путь. Но я иду в сторону своего IFC-ридера, а возможно, и редактора. Мы живём в интересное время. Сейчас я один могу делать столько же, сколько 30 лет назад делал весь Autodesk или Graphisoft (в плане разработки, не маркетинга). Поэтому мне не жалко написать один модуль, чтобы человеку стало весело. Я не гонюсь за авторским правом, потому что всё это придумано не мной и давно написано, я учусь сам и показываю другим людям.
Альтернативное времяпрепровождение - это прикольные видосики в телефоне. В лучшем случае просто тупые, в худшем - пропагандистские. Я выбрал другой путь. Так узнаю много нового (технологически нового) и держу мозг в тонусе. И я не знаю, где пригодятся мои знания в будущем. Уже сейчас я держу в руках связку WinAPI-Vulkan-GLSL, причём на чистом ассемблере, без третьих библиотек. Считаю матрицы преобразований с помощью AVX и FMA. В реальной жизни это мне никогда не пригодится. Почти наверное. Никто даже не знает, что я это умею. А что если я по приколу напишу свой мини-Revit, который будет соответствовать ГОСТам? Тупо чтобы потроллить Нанософт и Аскон? Задача по линии VK и OpenGL сейчас единая - мне нужен парсер obj -> VBO, причём не просто какой-нибудь, а хороший, годный - построенный на векторных инструкциях. Когда будет парсер, будет и визуализация
ml64, мне кажется, что на картинке девочка-подросток с большой грудью или у «Москвича» размер «Чайки»? размеры«Чайка»«Москвич 408»длина5600 мм4090 ммширина2000 мм1550 ммвысота1620 мм1480 ммколёсная база3250 мм2400 мм
А мне дед старые фотографии показывал, когда были в моде "инвалидки", как в старой комедии "самогонщики". Вот такую бы подкрасить, подсветить и с такой девушкой сфотографировать.
Точно, именно мотоколяска. У вас нет такой в вашей кодовой базе ? Тогда сразу оформляем спецзаказ, с полной предоплатой. Девушки только могут подвести. Они на такой позор могут не подписаться - ни за какие деньги.
GRAFik, «Операция Ы» — мотоколяска СМЗ С-3А «Кавказская пленница» — немецкий Adler Triumpf Junior 1937 года
Однозначно - да. На ЯВУ - проще. Но cmd = parts(i) - это действия, которые повторяются каждый байт (при каждом проходе): 1) вычисление адреса parts - это два лишних обращения к памяти: lea rax,parts add parts,i 2) весь перебор if cmd == 'v' и др. - это побайтовый опрос. Это долго. Для "Москвича" это не критично. Но мои объекты - это здания из огромного количества полигонов. Здесь нужны векторные возможности процессора, например, VPCMPISTRI Эта инструкция поможет определить, есть ли в строке нужные токены, а дальше можно уже по байтам разбирать, как с ними работать. Над чем я сейчас работаю: Сколько памяти выделить для буфера вершин? Можно взять с запасом, но это только для "Москвича" годится. Значит, надо считать. Как считать? Ищем строки, которые начинаются с "v". Но это могут быть и "v" с пробелом, и "v" с Tab, и "vn", и "vt". Как мы ищем? С помощью CMPSB? Нет, конечно. CMPSD? Но она сравнивает последовательность байт с последовательностью байт Берём VPCMPISTRI. Она сравнивает независимые 16 байт образца с последовательными 16 байтами памяти и ставит флаги, если находит первый совпавший байт. Очень удобно искать конец строки, если токен уже отработан или не нужен. Это база, а дальше - на любителя. Можно первым проходом заменить весь Tab на пробелы, можно удалить ведущие пробелы в начале строк, можно этого не делать, а просто посчитать количество токенов каждого типа и под них выделить нужное количество буферной памяти, можно идти в лоб и писать сразу в буфер VBO, но тогда получится монолитный объект, и я не смогу, например, открывать капот и двери "Москвича" Так что ЯВУ - это хорошо, но это не наш метод. Если бы я хотел просто и быстро крутить модель, я открыл бы её в SketchUp А мне интересен сам механизм.
Думал я думал - что это? Даже VPCMPISTRI посмотрел в сети - ничего не понял. Код (ASM): Mov eDi, Начальный Адрес Mov eCx, Сколько ClD / StD ; - направление Mov Al, 'v' ; что RepNE ScasB MovZx eBx, Byte ptr [eDi] BT [BitTab], eBx ; по битмап допустимых после 'v' байт ??
SCASB обращается к памяти каждый раз. Точнее, сейчас уже Intel это как-то кэширует (именно "как-то", т.к. это делает их тайный микрокод). Рассмотрим конкретный пример. Пример возьмём попроще - из широко известного в узких кругах пакета tinyobj_loader - см. вложение. Мне нужно узнать, сколько выделять памяти под буфер, т.е. сколько в исходном .obj-файле вершин, нормалей, полигонов, объектов, групп и прочих элементов. Моя задача - не найти символ "v" в строке, а подсчитать количество токенов "v", "vn", "vt", "o", "g" и т.д. в двух файлах: "геометрия.obj" и "материалы.mat". Причём даже встроенный в 3ds Max конвертер сохраняет данные, мягко говоря, не совсем удобно. Он может добавить Tab в начало строки, а может не добавить, он может задать вершины, а только потом объявить токен "o" ("объект"), так, что непонятно, к какому объекту эти вершины потом отнести. Мой алгоритм: Я беру первый значащий символ в строке, сравниваю его с пробелом, табом, LF, CR, EoF, # (комментарий в формате obj) - для каждого случая свой обработчик. Всё. Я определил, что символ значащий, дальше его обрабатываю. Если "o", то увеличиваю счётчик объектов, если "v", то увеличиваю счётчик вершин и т.д. А теперь представьте, что я для этого использую SCASB. 1) Мне надо для каждого токена запускать свой цикл сканирования (а если файл 20 мегабайт? Это не редкость для современной графики) 2) а если v - это не токен, а часть имени объекта, например, o vase - объект "ваза"? Сейчас я просто ищу начало строки, беру в ней первый значащий символ и сравниваю байт (или слово) по адресу rcx с ASCII-кодом первого символа токена: Спойлер: Побитовое сканирование Код (ASM): countObjEntities proc LOCAL pCurrentPosition:QWORD,hHeap:QWORD LOCAL nObjectIndex:DWORD,nMaterialIndex:DWORD PROLOG 100h ;Set the Local Pointer mov rcx,gpObjDataStart mov pCurrentPosition,rcx ;Initialize Counters mov gnTotalObjects,0 mov gnTotalGroups,0 mov gnTotalSubGroups,0 mov gnTotalVertices,0 mov gnTotalNormals,0 mov gnTotalFaces,0 mov gnTotalMtlLibs,0 mov gnTotalIndices,0 ;Initialize Indices mov nObjectIndex,0 lbl_NextLine: mov rcx,pCurrentPosition cmp rcx,gpObjDataEnd jge lbl_EndOfFile ;Check for consistency mov al,byte ptr[rcx] cmp al,0 ;EOF je lbl_EndOfFile cmp al,9 ;Tab je lbl_SkipByte cmp al,0Ah ;CR je lbl_SkipByte cmp al,0Dh ;LF je lbl_SkipByte cmp al,20h ;Space je lbl_SkipByte cmp al,23h ;# OBJ Format Comment je lbl_SkipByte ;Token detection. Arranged by Probability ;mov rcx,pCurrentPosition cmp byte ptr[rcx],76h ;"v" je lbl_TokenStartsWithV cmp byte ptr[rcx],66h ;"f" je lbl_TokenFace cmp dword ptr[rcx],6D657375h ;"usem" in reverse order - part of "usemtl" je lbl_TokenUseMtl cmp byte ptr[rcx],6Fh ;"o" je lbl_TokenObject cmp byte ptr[rcx],67h ;"g" je lbl_TokenGroup cmp dword ptr[rcx],6C6C746Dh; "mtll" in reverse order - part of "mtllib" je lbl_TokenMtlLib jmp lbl_SkipByte ;Count object name: o name lbl_TokenObject: add pCurrentPosition,2 ;skip "o " inc gnTotalObjects jmp lbl_SkipByte ;Count object name: g name lbl_TokenGroup: add pCurrentPosition,2 ;skip "g " inc gnTotalGroups jmp lbl_SkipByte ;Count material: "usemtl" lbl_TokenUseMtl: add pCurrentPosition,7 ;skip "usemtl " inc gnTotalSubGroups jmp lbl_SkipByte ;Branch V lbl_TokenStartsWithV: inc pCurrentPosition cmp byte ptr[rcx],6Eh ;"n" je lbl_TokenNormal cmp byte ptr[rcx],74h ;"t" je lbl_TokenTextureCoordinate ;jmp lbl_TokenVertex ;By default ;Count vertex: v x y z lbl_TokenVertex: inc pCurrentPosition ;skip "v " inc gnTotalVertices jmp lbl_SkipByte ;Count normal: vn x y z lbl_TokenNormal: add pCurrentPosition,2 ;skip "vn " inc gnTotalNormals jmp lbl_SkipByte ;Count texture: vt u v w lbl_TokenTextureCoordinate: add pCurrentPosition,2 ;skip "vt " inc gnTotalTextureCoords jmp lbl_SkipByte ;Count face: f v/vt/vn v/vt/vn v/vt/vn lbl_TokenFace: inc pCurrentPosition ;skip "f " inc gnTotalFaces jmp lbl_SkipByte ;Count material: "mtllib" lbl_TokenMtlLib: add pCurrentPosition,7 ;skip "mtllib " inc gnTotalMtlLibs jmp lbl_SkipByte lbl_SkipByte: mov rcx,pCurrentPosition cmp rcx,gpObjDataEnd jge lbl_EndOfFile mov al,byte ptr[rcx] cmp al,0 ;EOF je lbl_EndOfFile cmp al,0Ah ;LF je lbl_SkipLF cmp al,0Dh ;CR je lbl_SkipCR inc pCurrentPosition jmp lbl_SkipByte lbl_SkipCR: inc pCurrentPosition mov rcx,pCurrentPosition cmp byte ptr[rcx],0Ah ;LF je lbl_SkipLF jmp lbl_NextLine lbl_SkipLF: inc pCurrentPosition jmp lbl_NextLine lbl_EndOfFile: cmp gnTotalObjects,0 jne @f LOG_TEXT szErrNoObjects @@: cmp gnTotalGroups,0 jne @f LOG_TEXT szErrNoGroups @@: cmp gnTotalSubGroups,0 jne @f LOG_TEXT szErrNoSubGroups @@: cmp gnTotalMtlLibs,0 jne @f LOG_TEXT szErrNoMtlLibs @@: cmp gnTotalVertices,0 jne @f LOG_TEXT szErrNoVertices @@: cmp gnTotalNormals,0 jne @f LOG_TEXT szErrNoNormals @@: cmp gnTotalTextureCoords,0 jne @f LOG_TEXT szErrNoTextureCoords @@: cmp gnTotalFaces,0 jne @f LOG_TEXT szErrNoFaces @@: ;Success mov rax,1 jmp lbl_End lbl_WinError: call SpellWinError xor rax,rax ;jmp lbl_End lbl_End: EPILOG countObjEntities endp Но это неудобно тем, что я для чтения каждого байта обращаюсь к памяти, гоняю этот несчастный байт по всем шинам и контроллерам. SSE предлагает хорошее решение - VPCMPISTRI: Я создаю в памяти шаблон поиска - 4, 8 или 16 байт для интересующих меня символов, т.е. для поиска начала строки и пропуска пустышек это 10h, 13h, 20h, 9, для обработки токенов это ASCII-коды o, g, v, m (первые буквы токенов). А дальше - дело техники: xmm0 = шаблон поиска, xmm1 или адрес в памяти - сканируемый участок, imm = маска (ANSI или Unicode и другие настройки). Как только VPCMPISTRI встречает в заданном диапазоне любой символ из шаблона, она возвращает в RCX смещение этого символа и устанавливает CF. Кстати, если она встречает в заданном диапазоне 0, то устанавливает ZF, что очень важно, т.к. можно проскочить EoF. Вот над этим я и работаю: массово сканирую в поисках 10h, 13h, 20h, 9, а дальше точечно обрабатываю значимые данные. Надеюсь, понятно разложил.
Да... Команда нужная! т.е. поменять Mov Al, 'v' на Mov Al, 10 (после конца предыдущей - начало следующей). Непонятно почему, несмотря на Вы приводите массивные примеры "Case"- программирования "If"-ами? Вместо: Код (ASM): Mov eSi, Что разобрать Mov eCx, Сколько максимально - ; Т.Е. длина файла eCx и ([eSi+eCx]=0=EoF) должен находиться ; "вне" файла и рассмотрение его избыточно ! ClD ; / StD - направление xOr eAx, eAx LEA eBx, [BitMap1] LEA eDx, [CallTab1] ; или [JmpTab1] _1: LodsB ; например BT [eBx], eAx ;; JnC _2 ; возможно ускоряет, если мало токенов в обработке Call [eDx + eAx * 4] ; если надо почитабельнее ; Jmp [eDx + eAx * 4] ; как в вашем случае с "возвратом" Jmp _2 _2: LoopNZd _1 естественно при работе внутри цикла (_1 eBx, eDx и даже eSi c eCx могут меняться для следующих этапов "дерева" разбора! Простой пример: попался комментарий '#' Код (ASM): _NextLine Proc ; для Call [eDx + eAx * 4] Mov eDi, eSi Mov Al, 10 ; конец строки RepNE ScasB Mov eSi, eDi Test eCx, eCx Ret _NextLine EndP Возникает простой вопрос - может ли быть более 1-го токена в строке? Если не может, то после него... до конца строки включительно рассматривать нечего, в вами приведенном коде, и т.д. и т.п. Я пока не планирую процессор мощнее (E8400 у меня), но интересно, ~во сколько VPCMPISTRI быстрее ее аналога выше? P.S. От ошибок никто не застрахован.((( Либо 13,10, либо 0Dh, 0Ah.
Сначала по другому было: Код (ASM): Mov eSi, Что разобрать Mov eCx, Сколько максимально - ; Т.Е. длина файла eCx и ([eSi+eCx]=0=EoF) должен находиться ; "вне" файла и рассмотрение его избыточно ! ClD ; / StD - направление xOr eAx, eAx LEA eDx, [CallTab1] ; или [JmpTab1] _1: LodsB ; например Call [eDx + eAx * 4] ; если надо почитабельнее ; Jmp [eDx + eAx * 4] ; как в вашем случае с "возвратом" на _2 ; в JmpTab1 dd _2 ; для необрабатывемых _2: LoopD _1 _NextLine Proc ; для Call [eDx + eAx * 4] Mov eDi, eSi Mov Al, 10 ; конец строки RepNE ScasB Mov eSi, eDi Cmp eCx, 1 ;; AdC eCx, 0 ; так надежнее при возврате на _2: Ret _NextLine EndP потом подумал улучшить... и в конце - 1-ну инструкцию "пожалел" и ошибся - может неконролируемо вывалиться: Код (ASM): .... xOr eAx, eAx ; ZF=1 в самом начале по JnC _2 .... JnC _2 .... _2: LoopNZd _1 ; при уходе на ..... _Ret Proc ; для Call [eDx + eAx * 4] Ret _Ret EndP .... ;или в при JmpTab1 dd _2 ; для необрабатывемых P.S. У меня сейчас есть задачи обработки строк методом проб и ошибок))) поэтому возможно даже излишне активен в теме.
Можно сделать надежно(элитный способ) для подгрузки сторонних проектов(если не известно какое будет форматирование/мусорные токены), но при этом будет больше вычислений, сам код при этом будет гораздо лаконичнее. Либо 2 варианта загрузки(я так обычно не делаю): быстрый(обработка переносов/мусорных токенов - как у вас) и медленный(максимально надежный, но там немного другая логика).
А в этом и особенность низкоуровневого подхода. Когда я пишу на ассемблере, я не думаю о конструкциях Select Case, While или If. Это касается и организации главного цикла, и обработчика сообщений, и парсера. Когда я обрабатываю сообщения, я просто пробегаю по условиям в порядке их вероятности и/или критичности: Код (ASM): ;Messages Arranged by Probability cmp edx,111h je lbl_wmCommand ;Самое ожидаемое cmp edx,5 je lbl_wmSize ;Приходит несколько раз cmp edx,10h je lbl_wmClose ;Один либо несколько раз, если юзер передумает cmp edx,2 je lbl_wmDestroy ;Ждём единственного случая cmp edx,1 je lbl_wmCreate ;Один раз в начале, дальше - балласт ;None of the Above call DefWindowProcA Аналогично в парсере: Код (ASM): ;Token detection. Arranged by Probability ;mov rcx,pCurrentPosition cmp byte ptr[rcx],76h ;"v" - Вершин будут десятки тысяч je lbl_TokenStartsWithV cmp byte ptr[rcx],66h ;"f" - Полигонов будут тысячи je lbl_TokenFace cmp byte ptr[rcx],67h ;"g" - Несколько десятков групп je lbl_TokenGroup cmp dword ptr[rcx],6D657375h ;"usem" in reverse order - part of "usemtl" - Материалов наберётся с десяток je lbl_TokenUseMtl cmp byte ptr[rcx],6Fh ;"o" - Несколько объектов je lbl_TokenObject cmp dword ptr[rcx],6C6C746Dh; "mtll" in reverse order - part of "mtllib" - Одна или пара библиотек je lbl_TokenMtlLib jmp lbl_SkipByte Но: до разбора токенов надо ещё добраться! Допустим, я на первом проходе считаю количество токенов, чтобы узнать размер выделяемой памяти. Вот типичная строка: f -97/-112/-128 -81/-88/-104 -82/-89/-105 Я увидел токен f и увеличил inc gnTotalFaces. После этого остаток строки мне не нужен. Теперь мне нужно искать 13h,10h либо EoF. Я не буду гонять через память каждый байт инструкциями lodsb/cmpsb, я лучше загружу сразу 16 байт и проверю их внутри CPU. Вопрос по существу. Вразумительных отчётов я не видел (хотя и не искал особо), мне сейчас интереснее проверить на своём опыте. Очень может быть, что Вы правы, и для перебора строк лучше побайтовые команды. Но у меня сейчас такое приложение, в котором мозги уже переключились на векторный подход, поэтому хочется выдержать чистоту жанра.