Москвич-408 — элегантность, ставшая классикой

Тема в разделе "Vulkan", создана пользователем ml64, 25 июл 2026.

  1. Application

    Application Moderator Команда форума

    Публикаций:
    1
    Регистрация:
    8 дек 2007
    Сообщения:
    998
    Если называть ЯВУ все что не является ассемблером(например си): на ЯВУ есть свой кайф при программировании.
    Например если твой код не зависит от внешних библиотек/фреймворков это тоже кайф.
     
  2. ml64

    ml64 Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    82
    А вот тут Intel был ни при чём. Это был типичный ассемблерный ночной баг.
    Нашёл, исправил.

    Мне одному не дают спать Вулкан и ассемблер по ночам?

    RR_07.jpeg
     

    Вложения:

    Последнее редактирование: 12 авг 2026 в 18:16
    Mikl___ и Application нравится это.
  3. GRAFik

    GRAFik Active Member

    Публикаций:
    0
    Регистрация:
    14 мар 2020
    Сообщения:
    539
    Нет я тоже по всем ночам не сплю - всё жду когда вы мне, под мой музейный экспонат процессора, код на MASM64 - для OpenGL напишите. :)
     
  4. ml64

    ml64 Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    82
    Раз обещал - нехорошо заставлять ждать.
    Вообще я хотел сначала закончить парсер obj -> VBO, а потом перенести его в OpenGL-рамку, но если Вас терзает бессонница, то придётся отвлечься
     
  5. Application

    Application Moderator Команда форума

    Публикаций:
    1
    Регистрация:
    8 дек 2007
    Сообщения:
    998
    GRAFik, у вас есть хотябы 1 проект, который вы сами написали и выложили на васме, пусть даже при помощи ллм?
     
  6. Application

    Application Moderator Команда форума

    Публикаций:
    1
    Регистрация:
    8 дек 2007
    Сообщения:
    998
    Я бы не отвлекался и на этого сетевого хомяка болт бы положил :) Он же вам деньги не платит?
    Какое имеет моральное право критиковать и отвлекать от творчества/лезть не в свое дело?
     
  7. GRAFik

    GRAFik Active Member

    Публикаций:
    0
    Регистрация:
    14 мар 2020
    Сообщения:
    539
    Application, что за менторская манера общения? Модераторская власть что-ли отпечаток накладывает? И на брудершафт мы с вами, вроде, не пили. Так, что повежливее нужно быть с своими зарегистрированными пользователями/форумчанами - их у вас и так, как кот наплакал...
     
  8. ml64

    ml64 Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    82
    Здесь наши интересы совпадают: мой движок OpenGL основан на технологиях прошлого века (OpenGL 1.0).
    Работать с реальными 3D-моделями он просто не способен. Поэтому мне нужно прикрутить к нему парсер VAO/VBO/EBO.
    Если при этом он кому-то будет полезен, то я только буду рад.
    Vulkan - штука нестабильная и требует наличия библиотеки. OpenGL под Windows запускается из коробки.
    У самурая нет цели, есть только путь. Но я иду в сторону своего IFC-ридера, а возможно, и редактора.
    Мы живём в интересное время.
    Сейчас я один могу делать столько же, сколько 30 лет назад делал весь Autodesk или Graphisoft (в плане разработки, не маркетинга).
    Поэтому мне не жалко написать один модуль, чтобы человеку стало весело.
    Я не гонюсь за авторским правом, потому что всё это придумано не мной и давно написано, я учусь сам и показываю другим людям.
     
    GRAFik и Mikl___ нравится это.
  9. Application

    Application Moderator Команда форума

    Публикаций:
    1
    Регистрация:
    8 дек 2007
    Сообщения:
    998
    Имхо, это как дуршлаг водой наполнять. В пустую потраченное время/энергия.
     
  10. ml64

    ml64 Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    82
    Альтернативное времяпрепровождение - это прикольные видосики в телефоне.
    В лучшем случае просто тупые, в худшем - пропагандистские.
    Я выбрал другой путь. Так узнаю много нового (технологически нового) и держу мозг в тонусе.
    И я не знаю, где пригодятся мои знания в будущем.
    Уже сейчас я держу в руках связку WinAPI-Vulkan-GLSL, причём на чистом ассемблере, без третьих библиотек.
    Считаю матрицы преобразований с помощью AVX и FMA.
    В реальной жизни это мне никогда не пригодится. Почти наверное. Никто даже не знает, что я это умею.
    А что если я по приколу напишу свой мини-Revit, который будет соответствовать ГОСТам?
    Тупо чтобы потроллить Нанософт и Аскон?

    Задача по линии VK и OpenGL сейчас единая - мне нужен парсер obj -> VBO, причём не просто какой-нибудь, а хороший, годный - построенный на векторных инструкциях.
    Когда будет парсер, будет и визуализация
     
    Последнее редактирование: 13 авг 2026 в 03:14
    Mikl___ нравится это.
  11. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.279
    ml64,
    мне кажется, что на картинке девочка-подросток с большой грудью или у «Москвича» размер «Чайки»? :yes3:
    размеры«Чайка»«Москвич 408»
    длина5600 мм4090 мм
    ширина2000 мм1550 мм
    высота1620 мм1480 мм
    колёсная база3250 мм2400 мм
     

    Вложения:

    Последнее редактирование: 13 авг 2026 в 09:22
    ml64 нравится это.
  12. ml64

    ml64 Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    82
    Исправил
    RR_07a.jpeg
     
    Mikl___ и Application нравится это.
  13. GRAFik

    GRAFik Active Member

    Публикаций:
    0
    Регистрация:
    14 мар 2020
    Сообщения:
    539
    А мне дед старые фотографии показывал, когда были в моде "инвалидки", как в старой комедии "самогонщики". Вот такую бы подкрасить, подсветить и с такой девушкой сфотографировать. :)
     
  14. ml64

    ml64 Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    82
    Самогонщики сами бегали. Причём на лыжах, сквозь берёзу. Мотоколяска была в "Операции "Ы"
     
  15. GRAFik

    GRAFik Active Member

    Публикаций:
    0
    Регистрация:
    14 мар 2020
    Сообщения:
    539
    Точно, именно мотоколяска. У вас нет такой в вашей кодовой базе ? Тогда сразу оформляем спецзаказ, с полной предоплатой. Девушки только могут подвести. Они на такой позор могут не подписаться - ни за какие деньги. :)
     
  16. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.279
    GRAFik,
    «Операция Ы» — мотоколяска СМЗ С-3А
    «Кавказская пленница» — немецкий Adler Triumpf Junior 1937 года
     
  17. Application

    Application Moderator Команда форума

    Публикаций:
    1
    Регистрация:
    8 дек 2007
    Сообщения:
    998
    Может такое проще на python делать? В асм передавать сконвертированные файлы.
    Код (Python):
    1. import numpy as np
    2. import struct
    3. from pathlib import Path
    4. from typing import List, Tuple, Dict, Optional
    5. from collections import defaultdict
    6. import argparse
    7. import sys
    8.  
    9. class OBJParser:
    10.     """
    11.    Чистый парсер OBJ в VBO/EBO без привязки к рендерингу
    12.    Использует векторизованные операции через NumPy
    13.    """
    14.  
    15.     def __init__(self):
    16.         # Сырые данные
    17.         self.vertices = []      # v x y z
    18.         self.normals = []       # vn x y z
    19.         self.texcoords = []     # vt u v
    20.         self.faces = []         # f v/vt/vn ...
    21.  
    22.         # Результат
    23.         self.vbo_data = None    # Interleaved vertex data [x,y,z,nx,ny,nz,u,v]
    24.         self.ebo_data = None    # Indices
    25.         self.vertex_count = 0
    26.         self.index_count = 0
    27.  
    28.     def parse(self, filepath: str) -> 'OBJParser':
    29.         """
    30.        Парсинг OBJ файла
    31.        """
    32.         self.vertices = []
    33.         self.normals = []
    34.         self.texcoords = []
    35.         self.faces = []
    36.  
    37.         with open(filepath, 'r', encoding='utf-8') as f:
    38.             for line in f:
    39.                 line = line.strip()
    40.                 if not line or line.startswith('#'):
    41.                     continue
    42.              
    43.                 parts = line.split()
    44.                 cmd = parts[0]
    45.          
    46.                 if cmd == 'v':
    47.                     # v x y z
    48.                     self.vertices.append([float(parts[1]), float(parts[2]), float(parts[3])])
    49.              
    50.                 elif cmd == 'vn':
    51.                     # vn x y z
    52.                     self.normals.append([float(parts[1]), float(parts[2]), float(parts[3])])
    53.              
    54.                 elif cmd == 'vt':
    55.                     # vt u v
    56.                     u = float(parts[1])
    57.                     v = float(parts[2]) if len(parts) > 2 else 0.0
    58.                     self.texcoords.append([u, v])
    59.              
    60.                 elif cmd == 'f':
    61.                     # f v1/vt1/vn1 v2/vt2/vn2 ...
    62.                     face = []
    63.                     for i in range(1, len(parts)):
    64.                         indices = parts[i].split('/')
    65.                         v_idx = int(indices[0]) - 1
    66.                         vt_idx = int(indices[1]) - 1 if len(indices) > 1 and indices[1] else -1
    67.                         vn_idx = int(indices[2]) - 1 if len(indices) > 2 and indices[2] else -1
    68.                         face.append((v_idx, vt_idx, vn_idx))
    69.                     self.faces.append(face)
    70.  
    71.         # Конвертируем в NumPy для векторизации
    72.         self.vertices = np.array(self.vertices, dtype=np.float32)
    73.         self.normals = np.array(self.normals, dtype=np.float32) if self.normals else None
    74.         self.texcoords = np.array(self.texcoords, dtype=np.float32) if self.texcoords else None
    75.  
    76.         return self
    77.  
    78.     def compute_normals(self) -> np.ndarray:
    79.         """
    80.        Векторизованное вычисление нормалей через NumPy
    81.        """
    82.         if self.normals is not None:
    83.             return self.normals
    84.      
    85.         print("Computing normals...")
    86.  
    87.         # Собираем треугольники
    88.         triangles = []
    89.         for face in self.faces:
    90.             if len(face) >= 3:
    91.                 # Триангуляция веером
    92.                 for i in range(1, len(face) - 1):
    93.                     v0 = face[0][0]
    94.                     v1 = face[i][0]
    95.                     v2 = face[i + 1][0]
    96.                     if v0 >= 0 and v1 >= 0 and v2 >= 0:
    97.                         triangles.append((v0, v1, v2))
    98.  
    99.         if not triangles:
    100.             raise ValueError("No triangles found in OBJ")
    101.      
    102.         triangles = np.array(triangles, dtype=np.int32)
    103.  
    104.         # Векторизованное вычисление
    105.         v0 = self.vertices[triangles[:, 0]]
    106.         v1 = self.vertices[triangles[:, 1]]
    107.         v2 = self.vertices[triangles[:, 2]]
    108.  
    109.         edge1 = v1 - v0
    110.         edge2 = v2 - v0
    111.         normals = np.cross(edge1, edge2)
    112.  
    113.         # Нормализация
    114.         lengths = np.sqrt(np.sum(normals * normals, axis=1, keepdims=True))
    115.         lengths = np.where(lengths == 0, 1, lengths)
    116.         normals = normals / lengths
    117.  
    118.         # Агрегация по вершинам
    119.         vertex_normals = np.zeros_like(self.vertices)
    120.         np.add.at(vertex_normals, triangles[:, 0], normals)
    121.         np.add.at(vertex_normals, triangles[:, 1], normals)
    122.         np.add.at(vertex_normals, triangles[:, 2], normals)
    123.  
    124.         # Нормализация итоговых нормалей
    125.         lengths = np.sqrt(np.sum(vertex_normals * vertex_normals, axis=1, keepdims=True))
    126.         lengths = np.where(lengths == 0, 1, lengths)
    127.         vertex_normals = vertex_normals / lengths
    128.  
    129.         return vertex_normals
    130.  
    131.     def build_vbo_ebo(self, deduplicate: bool = True) -> Tuple[np.ndarray, np.ndarray]:
    132.         """
    133.        Строит VBO и EBO
    134.  
    135.        Args:
    136.            deduplicate: Удалять ли дублирующиеся вершины
    137.  
    138.        Returns:
    139.            (vbo_data, ebo_data) - интерливированные данные и индексы
    140.        """
    141.         print(f"Building VBO/EBO (deduplicate={deduplicate})...")
    142.  
    143.         normals = self.compute_normals()
    144.  
    145.         # Подготовка UV
    146.         if self.texcoords is None:
    147.             texcoords = np.zeros((len(self.vertices), 2), dtype=np.float32)
    148.         else:
    149.             texcoords = self.texcoords
    150.  
    151.         if not deduplicate:
    152.             # Простой вариант: все вершины как есть
    153.             vertex_count = len(self.vertices)
    154.             vbo_data = np.zeros((vertex_count, 8), dtype=np.float32)
    155.             vbo_data[:, 0:3] = self.vertices
    156.             vbo_data[:, 3:6] = normals
    157.             vbo_data[:, 6:8] = texcoords
    158.      
    159.             # Индексы - просто последовательность
    160.             indices = []
    161.             for face in self.faces:
    162.                 for i in range(1, len(face) - 1):
    163.                     indices.append(face[0][0])
    164.                     indices.append(face[i][0])
    165.                     indices.append(face[i + 1][0])
    166.      
    167.             ebo_data = np.array(indices, dtype=np.uint32)
    168.      
    169.         else:
    170.             # С дедупликацией через хеширование
    171.             unique_vertices = []
    172.             index_map = {}
    173.             indices = []
    174.      
    175.             for face in self.faces:
    176.                 for i in range(1, len(face) - 1):
    177.                     v0, v1, v2 = face[0], face[i], face[i + 1]
    178.              
    179.                     for v in (v0, v1, v2):
    180.                         v_idx, vt_idx, vn_idx = v
    181.                  
    182.                         pos = self.vertices[v_idx]
    183.                         norm = normals[v_idx] if vn_idx == -1 else self.normals[vn_idx]
    184.                         tex = texcoords[vt_idx] if vt_idx != -1 else texcoords[v_idx]
    185.                  
    186.                         # Хеш с округлением для точности
    187.                         key = (
    188.                             round(pos[0], 6), round(pos[1], 6), round(pos[2], 6),
    189.                             round(norm[0], 6), round(norm[1], 6), round(norm[2], 6),
    190.                             round(tex[0], 6), round(tex[1], 6)
    191.                         )
    192.                  
    193.                         if key not in index_map:
    194.                             index_map[key] = len(unique_vertices)
    195.                             unique_vertices.append([
    196.                                 pos[0], pos[1], pos[2],
    197.                                 norm[0], norm[1], norm[2],
    198.                                 tex[0], tex[1]
    199.                             ])
    200.                  
    201.                         indices.append(index_map[key])
    202.      
    203.             vbo_data = np.array(unique_vertices, dtype=np.float32)
    204.             ebo_data = np.array(indices, dtype=np.uint32)
    205.  
    206.         self.vbo_data = vbo_data
    207.         self.ebo_data = ebo_data
    208.         self.vertex_count = len(vbo_data)
    209.         self.index_count = len(ebo_data)
    210.  
    211.         return vbo_data, ebo_data
    212.  
    213.     def save_binary(self, output_path: str, format: str = 'vbo'):
    214.         """
    215.        Сохраняет в бинарный файл
    216.  
    217.        Args:
    218.            output_path: Путь к выходному файлу
    219.            format: 'vbo' - стандартный, 'obj' - с заголовком
    220.        """
    221.         if self.vbo_data is None or self.ebo_data is None:
    222.             self.build_vbo_ebo()
    223.  
    224.         with open(output_path, 'wb') as f:
    225.             if format == 'vbo':
    226.                 # Формат: [vertex_count, index_count, stride] + данные
    227.                 f.write(struct.pack('III',
    228.                     self.vertex_count,
    229.                     self.index_count,
    230.                     8  # 8 float на вершину
    231.                 ))
    232.          
    233.                 # VBO данные (интерливированные вершины)
    234.                 f.write(self.vbo_data.tobytes())
    235.          
    236.                 # EBO данные (индексы)
    237.                 f.write(self.ebo_data.tobytes())
    238.          
    239.             else:  # 'raw'
    240.                 # Только данные без заголовка
    241.                 f.write(self.vbo_data.tobytes())
    242.                 f.write(self.ebo_data.tobytes())
    243.  
    244.         print(f"Saved to {output_path}")
    245.         print(f"  Vertices: {self.vertex_count}")
    246.         print(f"  Indices: {self.index_count}")
    247.         print(f"  VBO size: {self.vbo_data.nbytes / 1024 / 1024:.2f} MB")
    248.         print(f"  EBO size: {self.ebo_data.nbytes / 1024 / 1024:.2f} MB")
    249.  
    250.     def save_obj_optimized(self, output_path: str):
    251.         """
    252.        Сохраняет оптимизированный OBJ с уникальными вершинами
    253.        """
    254.         if self.vbo_data is None or self.ebo_data is None:
    255.             self.build_vbo_ebo()
    256.  
    257.         with open(output_path, 'w') as f:
    258.             f.write("# Optimized OBJ\n")
    259.             f.write(f"# Vertices: {self.vertex_count}\n")
    260.             f.write(f"# Triangles: {self.index_count // 3}\n\n")
    261.      
    262.             # Записываем вершины
    263.             for i in range(self.vertex_count):
    264.                 v = self.vbo_data[i]
    265.                 f.write(f"v {v[0]:.6f} {v[1]:.6f} {v[2]:.6f}\n")
    266.      
    267.             # Записываем нормали
    268.             for i in range(self.vertex_count):
    269.                 v = self.vbo_data[i]
    270.                 f.write(f"vn {v[3]:.6f} {v[4]:.6f} {v[5]:.6f}\n")
    271.      
    272.             # Записываем UV
    273.             for i in range(self.vertex_count):
    274.                 v = self.vbo_data[i]
    275.                 f.write(f"vt {v[6]:.6f} {v[7]:.6f}\n")
    276.      
    277.             # Записываем грани
    278.             f.write("\n")
    279.             for i in range(0, self.index_count, 3):
    280.                 i0, i1, i2 = self.ebo_data[i:i+3] + 1  # +1 для индексации с 1
    281.                 f.write(f"f {i0}/{i0}/{i0} {i1}/{i1}/{i1} {i2}/{i2}/{i2}\n")
    282.  
    283.         print(f"Saved optimized OBJ to {output_path}")
    284.  
    285.     def get_stats(self) -> Dict:
    286.         """
    287.        Возвращает статистику по модели
    288.        """
    289.         stats = {
    290.             'vertices_raw': len(self.vertices),
    291.             'faces': len(self.faces),
    292.             'normals_raw': len(self.normals) if self.normals is not None else 0,
    293.             'texcoords_raw': len(self.texcoords) if self.texcoords is not None else 0,
    294.         }
    295.  
    296.         if self.vbo_data is not None:
    297.             stats.update({
    298.                 'vertices_unique': self.vertex_count,
    299.                 'indices': self.index_count,
    300.                 'triangles': self.index_count // 3,
    301.                 'vbo_bytes': self.vbo_data.nbytes,
    302.                 'ebo_bytes': self.ebo_data.nbytes,
    303.                 'total_bytes': self.vbo_data.nbytes + self.ebo_data.nbytes,
    304.             })
    305.  
    306.         return stats
    307.  
    308.     def load_binary(self, filepath: str) -> Tuple[np.ndarray, np.ndarray]:
    309.         """
    310.        Загружает бинарный VBO/EBO
    311.        """
    312.         with open(filepath, 'rb') as f:
    313.             # Читаем заголовок
    314.             header = f.read(12)
    315.             if len(header) == 12:
    316.                 vertex_count, index_count, stride = struct.unpack('III', header)
    317.             else:
    318.                 # Старый формат без заголовка - пробуем определить размер
    319.                 f.seek(0)
    320.                 data = f.read()
    321.                 # Пробуем интерпретировать как VBO данных
    322.                 # Это сложно, проще использовать формат с заголовком
    323.                 raise ValueError("Invalid or old format, use 'vbo' format")
    324.      
    325.             # Читаем VBO
    326.             vbo_size = vertex_count * stride * 4
    327.             vbo_data = np.frombuffer(f.read(vbo_size), dtype=np.float32)
    328.             vbo_data = vbo_data.reshape(vertex_count, stride)
    329.      
    330.             # Читаем EBO
    331.             ebo_data = np.frombuffer(f.read(index_count * 4), dtype=np.uint32)
    332.      
    333.             self.vbo_data = vbo_data
    334.             self.ebo_data = ebo_data
    335.             self.vertex_count = vertex_count
    336.             self.index_count = index_count
    337.      
    338.             return vbo_data, ebo_data
    339. # Командная строка
    340. def main():
    341.     parser = argparse.ArgumentParser(
    342.         description="OBJ to VBO/EBO parser with vectorized operations"
    343.     )
    344.     parser.add_argument("input", help="Input OBJ file")
    345.     parser.add_argument("-o", "--output", help="Output binary file", default="model.vbo")
    346.     parser.add_argument("--no-dedup", action="store_true",
    347.                        help="Disable vertex deduplication")
    348.     parser.add_argument("--format", choices=["vbo", "raw"], default="vbo",
    349.                        help="Output format")
    350.     parser.add_argument("--export-obj", help="Export optimized OBJ")
    351.     parser.add_argument("--stats", action="store_true",
    352.                        help="Show statistics only")
    353.  
    354.     args = parser.parse_args()
    355.  
    356.     # Парсим
    357.     print(f"Parsing {args.input}...")
    358.     parser = OBJParser()
    359.     parser.parse(args.input)
    360.  
    361.     if args.stats:
    362.         stats = parser.get_stats()
    363.         print("\n Statistics:")
    364.         for key, value in stats.items():
    365.             if 'bytes' in key or 'size' in key:
    366.                 print(f"  {key}: {value / 1024 / 1024:.2f} MB")
    367.             else:
    368.                 print(f"  {key}: {value}")
    369.         return
    370.  
    371.     # Строим VBO/EBO
    372.     parser.build_vbo_ebo(deduplicate=not args.no_dedup)
    373.  
    374.     # Сохраняем
    375.     parser.save_binary(args.output, format=args.format)
    376.  
    377.     # Экспорт в OBJ если нужно
    378.     if args.export_obj:
    379.         parser.save_obj_optimized(args.export_obj)
    380.  
    381.     # Показываем статистику
    382.     stats = parser.get_stats()
    383.     print("\n Final statistics:")
    384.     for key, value in stats.items():
    385.         if 'bytes' in key or 'size' in key:
    386.             print(f"  {key}: {value / 1024 / 1024:.2f} MB")
    387.         else:
    388.             print(f"  {key}: {value}")
    389. if __name__ == "__main__":
    390.     main()
     
  18. ml64

    ml64 Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    82
    Однозначно - да. На ЯВУ - проще.
    Но 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
    А мне интересен сам механизм.
     
    Последнее редактирование: 13 авг 2026 в 19:08
  19. R81...

    R81... Active Member

    Публикаций:
    0
    Регистрация:
    1 фев 2020
    Сообщения:
    192
    Думал я думал - что это? Даже VPCMPISTRI посмотрел в сети - ничего не понял.
    Код (ASM):
    1. Mov eDi, Начальный Адрес
    2. Mov eCx, Сколько
    3. ClD / StD ; - направление
    4. Mov Al, 'v' ; что
    5. RepNE ScasB
    6. MovZx  eBx, Byte ptr [eDi]
    7. BT  [BitTab], eBx  ; по битмап допустимых после 'v' байт
    8. ??
     
  20. ml64

    ml64 Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    82
    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):
    1. countObjEntities proc
    2. LOCAL pCurrentPosition:QWORD,hHeap:QWORD
    3. LOCAL nObjectIndex:DWORD,nMaterialIndex:DWORD
    4.  
    5. PROLOG 100h
    6.  
    7. ;Set the Local Pointer
    8. mov rcx,gpObjDataStart
    9. mov pCurrentPosition,rcx
    10.  
    11. ;Initialize Counters
    12. mov gnTotalObjects,0
    13. mov gnTotalGroups,0
    14. mov gnTotalSubGroups,0
    15. mov gnTotalVertices,0
    16. mov gnTotalNormals,0
    17. mov gnTotalFaces,0
    18. mov gnTotalMtlLibs,0
    19. mov gnTotalIndices,0
    20.  
    21. ;Initialize Indices
    22. mov nObjectIndex,0
    23.  
    24. lbl_NextLine:
    25.  
    26. mov rcx,pCurrentPosition
    27. cmp rcx,gpObjDataEnd
    28. jge lbl_EndOfFile
    29.  
    30. ;Check for consistency
    31. mov al,byte ptr[rcx]
    32. cmp al,0 ;EOF
    33. je  lbl_EndOfFile
    34. cmp al,9 ;Tab
    35. je  lbl_SkipByte
    36. cmp al,0Ah ;CR
    37. je  lbl_SkipByte
    38. cmp al,0Dh ;LF
    39. je  lbl_SkipByte
    40. cmp al,20h ;Space
    41. je  lbl_SkipByte
    42. cmp al,23h ;# OBJ Format Comment
    43. je  lbl_SkipByte
    44.  
    45. ;Token detection. Arranged by Probability
    46. ;mov rcx,pCurrentPosition
    47. cmp byte ptr[rcx],76h ;"v"
    48. je lbl_TokenStartsWithV
    49. cmp byte ptr[rcx],66h ;"f"
    50. je lbl_TokenFace
    51. cmp dword ptr[rcx],6D657375h ;"usem" in reverse order - part of "usemtl"
    52. je lbl_TokenUseMtl
    53. cmp byte ptr[rcx],6Fh ;"o"
    54. je lbl_TokenObject
    55. cmp byte ptr[rcx],67h ;"g"
    56. je lbl_TokenGroup
    57. cmp dword ptr[rcx],6C6C746Dh; "mtll" in reverse order - part of "mtllib"
    58. je lbl_TokenMtlLib
    59. jmp lbl_SkipByte
    60.  
    61. ;Count object name: o name
    62. lbl_TokenObject:
    63. add pCurrentPosition,2 ;skip "o "
    64. inc gnTotalObjects
    65. jmp lbl_SkipByte
    66.  
    67. ;Count object name: g name
    68. lbl_TokenGroup:
    69. add pCurrentPosition,2 ;skip "g "
    70. inc gnTotalGroups
    71. jmp lbl_SkipByte
    72.  
    73. ;Count material: "usemtl"
    74. lbl_TokenUseMtl:
    75. add pCurrentPosition,7 ;skip "usemtl "
    76. inc gnTotalSubGroups
    77. jmp lbl_SkipByte
    78.  
    79. ;Branch V
    80. lbl_TokenStartsWithV:
    81. inc pCurrentPosition
    82. cmp byte ptr[rcx],6Eh ;"n"
    83. je lbl_TokenNormal
    84. cmp byte ptr[rcx],74h ;"t"
    85. je lbl_TokenTextureCoordinate
    86. ;jmp lbl_TokenVertex ;By default
    87.  
    88. ;Count vertex: v x y z
    89. lbl_TokenVertex:
    90. inc pCurrentPosition ;skip "v "
    91. inc gnTotalVertices
    92. jmp lbl_SkipByte
    93.  
    94. ;Count normal: vn x y z
    95. lbl_TokenNormal:
    96. add pCurrentPosition,2 ;skip "vn "
    97. inc gnTotalNormals
    98. jmp lbl_SkipByte
    99.  
    100. ;Count texture: vt u v w
    101. lbl_TokenTextureCoordinate:
    102. add pCurrentPosition,2 ;skip "vt "
    103. inc gnTotalTextureCoords
    104. jmp lbl_SkipByte
    105.  
    106. ;Count face: f v/vt/vn v/vt/vn v/vt/vn
    107. lbl_TokenFace:
    108. inc pCurrentPosition ;skip "f "
    109. inc gnTotalFaces
    110. jmp lbl_SkipByte
    111.  
    112. ;Count material: "mtllib"
    113. lbl_TokenMtlLib:
    114. add pCurrentPosition,7 ;skip "mtllib "
    115. inc gnTotalMtlLibs
    116. jmp lbl_SkipByte
    117.  
    118. lbl_SkipByte:
    119. mov rcx,pCurrentPosition
    120. cmp rcx,gpObjDataEnd
    121. jge lbl_EndOfFile
    122. mov al,byte ptr[rcx]
    123. cmp al,0 ;EOF
    124. je lbl_EndOfFile
    125. cmp al,0Ah ;LF
    126. je lbl_SkipLF
    127. cmp al,0Dh ;CR
    128. je lbl_SkipCR
    129. inc pCurrentPosition
    130. jmp lbl_SkipByte
    131.  
    132. lbl_SkipCR:
    133. inc pCurrentPosition
    134. mov rcx,pCurrentPosition
    135. cmp byte ptr[rcx],0Ah ;LF
    136. je lbl_SkipLF
    137. jmp lbl_NextLine
    138.  
    139. lbl_SkipLF:
    140. inc pCurrentPosition
    141. jmp lbl_NextLine
    142.  
    143. lbl_EndOfFile:
    144. cmp gnTotalObjects,0
    145. jne @f
    146. LOG_TEXT szErrNoObjects
    147. @@:
    148. cmp gnTotalGroups,0
    149. jne @f
    150. LOG_TEXT szErrNoGroups
    151. @@:
    152. cmp gnTotalSubGroups,0
    153. jne @f
    154. LOG_TEXT szErrNoSubGroups
    155. @@:
    156. cmp gnTotalMtlLibs,0
    157. jne @f
    158. LOG_TEXT szErrNoMtlLibs
    159. @@:
    160. cmp gnTotalVertices,0
    161. jne @f
    162. LOG_TEXT szErrNoVertices
    163. @@:
    164. cmp gnTotalNormals,0
    165. jne @f
    166. LOG_TEXT szErrNoNormals
    167. @@:
    168. cmp gnTotalTextureCoords,0
    169. jne @f
    170. LOG_TEXT szErrNoTextureCoords
    171. @@:
    172. cmp gnTotalFaces,0
    173. jne @f
    174. LOG_TEXT szErrNoFaces
    175. @@:
    176.  
    177. ;Success
    178. mov rax,1
    179. jmp lbl_End
    180.  
    181. lbl_WinError:
    182. call SpellWinError
    183. xor rax,rax
    184. ;jmp lbl_End
    185.  
    186. lbl_End:
    187. EPILOG
    188. 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, а дальше точечно обрабатываю значимые данные.

    Надеюсь, понятно разложил.
     

    Вложения:

    • cornell_box.zip
      Размер файла:
      797 байт
      Просмотров:
      5
    Последнее редактирование: 14 авг 2026 в 01:34
    Mikl___ нравится это.