Word, запущенный в Windows 3.1 с удобным разрешением 1152х864

Много месяцев назад я попробовал свои силы в написании 256-цветного драйвера высокого разрешения для Windows 3.1. Попытка была успешной, но работа пока ещё не завершена. В процессе я заново открыл для себя множество забытых вещей и узнал ещё больше новых. Этот пост основан на заметках, которые я делал по ходу разработки.

▍ Исходный код и среда разработки


В качестве фундамента я взял пример 256-цветного драйвера Video 7 (V7) SuperVGA из Windows 3.1 DDK. Драйвер целиком написан на ассемблере (ого!) и состоит из десятков файлов исходного кода общим размером 1,5 МБ. Этот драйвер не был идеальным в качестве опорной точки, но определённо оказался лучшим из имеющихся в наличии.

Первым делом мне нужно было определиться со средой разработки. Хотя я мог сделать всё в VM, мне хотелось этого избежать. При разработке драйвера дисплея очевидно придётся много раз перезапускать Windows и неизбежно перезагружать машину, поэтому для удобства понадобилось бы не менее двух VM.

Поэтому вместо этого я решил настроить всё на моей хост-системе с 64-битной Windows 10. Запустить оригинальные 16-битные инструменты разработки я бы не смог, но это была лишь меньшая из проблем. Критически важным элементом был MASM 5.NT.02 — 32-битная версия MASM 5.1, спасённая из старого Windows NT SDK. Исходный код Windows 3.1 DDK сильно нацелен на MASM 5.1, и конвертация под другой ассемблер стала бы серьёзным проектом, который бы вызвал много багов.

К счастью, MASM 5.NT.02 работает вполне неплохо и ассемблирует исходный код без проблем. Для всего остального я использовал инструменты Open Watcom 1.9: wmake, wlink и wrc (утилита make, компоновщик и компилятор ресурсов). Чтобы перенести двоичный файл драйвера из хост-системы в VM, я использовал образ флоппи-диска. Это проще и быстрее, чем любые подключения по сети.

После того как всё стало собираться, началось настоящее веселье: я приступил к модификации драйвера Video 7, чтобы он заработал на другом «оборудовании».

▍ Трудности и лишения


К счастью, в примере драйвера было не так много кода, специфичного для Video 7. К сожалению, код для конкретного оборудования был разбросан по всей кодовой базе.

Первым делом я должен был унифицировать код переключения банков, критически важный для производительности. В примере драйвера была примерно полудюжина различных процедур переключения банков (и нет, я не знаю почему). Я заменил их на одну и сделал так, чтобы переключение банков выполнялось только при необходимости (например, когда текущий банк отличается от запрашиваемого).

Вы спросите, почему бы не использовать линейный буфер кадров (LFB)? С самого начала я не хотел ограничивать драйвер режимом Windows 386 Enhanced mode. Использовать LFB в Standard mode сложно. Перепрограммировать базу селекторов просто, но всё ломается, когда система работает с разбиением памяти на страницы, а LFB не отображается. Хуже того, в real mode использовать LFB просто невозможно.

Затем настало время мучительного удаления кода отрисовки, относящегося к V7. Его оказалось больше, чем я ожидал. Оборудование V7 имеет защёлки и регистры блиттинга фрагментов паттернов для ускорения некоторых операций. Кроме того, практически во всём коде есть пути полностью программной отрисовки на случай невозможности сделать это аппаратно, но не все они чётко помечены. Мне пришлось приложить некоторые усилия, чтобы принудительно пустить всю отрисовку по программному пути.

С этим был связан только один ужасно неприятный баг. Драйвер предполагал, что регистры блиттинга фрагментов паттернов выполняют поворот паттернов. Принудительное использование чисто программной отрисовки могло привести к ситуации, когда паттерн поворачивается некорректно. Это вызывало очень заметные визуальные проблемы при отрисовке окон, потому что прямоугольник выделения (линия паттерна) оставлял за собой «мусор».

Я отказался от всех попыток использовать в драйвере внеэкранную память. Хотя на реальном оборудовании это обычно приводит к росту производительности, в VM это не так. Из-за траты ресурсов на переключение банков перемещение данных из системной памяти всегда выполняется быстрее.

Код отрисовки в драйвере V7 подразумевает, что одна растровая строка никогда не пересекает границу банка. Это существенно упрощает логику отрисовки, поскольку отсутствует необходимость потенциального переключения банков между двумя соседними пикселями. В оригинальном драйвере использовался шаг в 1K, что обеспечивает максимальное разрешение по горизонтали в 1024 пикселя. Я изменил его на 2K, что позволило использовать разрешения до 2048 пикселей по горизонтали. Эту систему потенциально можно сделать гибче для экономии видеопамяти, но, по крайней мере, на текущий момент это не стоило таких усилий.

В коде отрисовки курсора мыши был ещё один неприятный баг. Драйвер V7 практически всегда использует аппаратный курсор и откат к программной отрисовке почти никогда или вообще никогда не используется. При определённых обстоятельствах вход в процедуру сохранения содержимого экрана под курсором может произойти с установленным флагом направления, что приведёт к копированию данных не в том направлении и к повреждению ни в чём не виноватой памяти. Проблему удалось решить единственным CLD в нужном месте.

Кроме того, мне пришлось разбираться с вопросом, почему цвета в моём драйвере отличаются от цветов в драйверах VGA/SVGA для Windows 3.1. Я узнал, что по какой-то причине драйвер 8514/A для Windows 3.0 и 3.1 (канонический драйвер высокого разрешения) на самом деле использовал другую цветовую схему. Это задокументировано в DDK Windows 3.0 и 3.1, но не приведено никакого объяснения, почему цвета должны отличаться.

▍ Развлечения с инструментами


Компоновщик (wlink) вызвал одну очень интересную проблему. По умолчанию wlink включает оптимизацию дальних вызовов, заменяя дальние вызовы близкими. Эта оптимизация почти всегда безопасна и повышает производительность, однако не в случае драйвера дисплея Windows. Драйвер «компилирует» процедуры отрисовки, копируя фрагменты кода из сегмента кода в стек, ассемблируя по необходимости их нужные части и изменяя константы в коде. wlink оптимизировал дальние вызовы внутри сегмента кода, что было замечательно, но когда этот код копировался в стек, вызовы сегмента кода на самом деле должны были быть дальними. Разобравшись, в чём была проблема, я просто отключил оптимизацию дальних вызовов.

В качестве побочного проекта я также написал довольно неоптимизированный инструмент wmapsym — функциональный эквивалент Microsoft MAPSYM, но использующий в качестве входных данных map-файлы Watcom. Это оказалось чрезвычайно полезно при отладке драйвера.

Кстати, об отладке — для отладки драйверов Windows 3.1 используется WDEB386, более-менее стандартный отладчик Microsoft, схожий с SYMDEB, отладчиком ядра OS/2, NTSD и другими. Я использовал его с перенаправлением ввода и вывода на последовательный порт. Он выполнял перенаправление в pipe на хост-машине и подключённый к pipe клиент PuTTY.

WDEB386 в PuTTY

▍ Ещё более ретро


Функциональность драйвера дисплея в Windows 3.1 Standard и 386 Enhanced не так уж важна. Единственная область, где она очень критична — это поддержка сессий DOS. В 386 Enhanced есть целый отдельный VxD (VDDVGA), занимающийся виртуализацией видео.

Даже при переключении в полноэкранную сессию DOS-драйвер дисплея остаётся активным в 386 Enhanced mode, но уведомления он получает при помощи вызовов dev_to_foreground (переключение на фон) и dev_to_background (возврат обратно). В Standard mode при переключении в полноэкранную сессию DOS-драйвер завершается при помощи вызова Disable, а при обратном процессе включается обратно при помощи Enable.

В Windows 3.0 всё становится ещё интереснее. В Standard и 386 Enhanced mode различия с Windows 3.1 минимальны. Однако Windows 3.0, работающая в real mode — это совершенно другое дело. Мне пришлось модифицировать драйвер так, чтобы он не использовал API, доступные только в protected mode, и решал, что делать, уже в среде выполнения (при помощи WinFlags).

Было бы здорово, если бы нашёлся пример драйвера Video 7 из Windows 3.0 DDK. Увы, мне не удалось его обнаружить.

Чтобы заставить драйвер работать в Windows 2.x, нужно было проделать ещё больше работы. Базовая структура драйвера осталась той же, в нём просто нужно реализовать меньшее количество вызовов GDI. По большей части, в real mode Windows 2.x чрезвычайно схожа с Windows 3.0. Разница в том, что вызовы API, добавленные для поддержки protected mode (например, AllocCSToDSAlias), совершенно отсутствуют в Windows 2.x. Код отрисовки, по сути, идентичен, однако инициализация и выгрузка должны быть немного другими.

Теоретически можно было бы даже импортировать специфические для Windows 3.x процедуры динамическим образом и использовать один двоичный файл для Windows 2.x и 3.x. На практике это неосуществимо, потому что драйверам также нужен разный формат ресурсов (Microsoft существенно изменила формат ресурсов между Windows 2.x и 3.0). Поэтому было гораздо проще создать отдельный двоичный драйвер Windows 2.x и использовать условную компиляцию для применения путей кода Windows 3.x или 2.x.

Дополнительная сложность здесь возникла и в том, что я не мог найти компилятор ресурсов, способный работать с ресурсами Windows 2.x и запускающийся в 32-битной Windows. Для финализации двоичного драйвера 2.x я прибег к запуску RC из Windows 2.x SDK в DOS VM. Некрасиво, но полностью функционально.

В конечном итоге, это было интересное путешествие в мир ретроразработки. И мне предстоит сделать ещё многое.

Играй в нашу новую игру прямо в Telegram!

Комментарии (13)


  1. MonkeyWatchingYou
    04.01.2023 17:04
    +10

    Какой приятный скриншот в заглавии. Всё интуитивно понятно, особенно мне теряющему зрение хорошо видны отдельные выпуклые элементы.

    За статью спасибо, вспомнил, понастольгировал, узнал новое (С опозданием в дцать лет).


  1. Writer
    04.01.2023 19:25
    +4

    Снимаю шляпу перед подобными проектами. Очень круто и познавательно! Интересно, много ли осталось случаев, когда требуется гонять Win 3.x на экранах с высоким разрешением?


    1. PuerteMuerte
      04.01.2023 23:30
      +5

      Как минимум, любой из нас, кто захотел бы поиграться с Вин 3.х


      1. emusic
        05.01.2023 17:06

        Win9x для этого не годится?


        1. PuerteMuerte
          05.01.2023 18:24
          +4

          Не очень, Win9x больше подходит для тех, кто захотел бы поиграться с Win9x :)


          1. emusic
            05.01.2023 18:28
            +1

            То есть, поиграться с самой виндой 3.x, как самоцель, а не каким-то софтом под нее? :)


            1. PuerteMuerte
              05.01.2023 18:45
              +1

              Ну естественно. Собирая ретро-окружение, ты обычно собираешь его вместе с конкретной операционкой, а не просто "ой, соскучился по Corel Draw 4, где б её запустить".


  1. KivApple
    04.01.2023 23:48

    Хуже того, в real mode использовать LFB просто неневозможно.т

    Технически существует unreal mode. Это, конечно, хак...


  1. axe_chita
    05.01.2023 05:29
    +1

    Меня терзают смутные воспоминания, что я видел в те времена драйвер Windows под VESA VBE
    Надо прошерстить старые диски/дискеты.


  1. Didimus
    05.01.2023 17:21

    Вот бы кто запилил драйвера на новое железо да под w7...


  1. friend001002
    05.01.2023 18:16
    +1

    Красота! Всегда почему-то тянуло к старому софту, да и железу. Как-то пытался что-то писать для DirectX ~4.x на VC++ ~4.2. Очень интересно было. А для Windows 1.x драйвер планируется? Или подойдёт версия для 2.x?


    1. rogoz
      05.01.2023 19:10

      В оригинале скриншот с подписью:

      That’s because it is Windows 1.04 running with a heavily modified 256-color Windows 3.x display driver, using resources from a Windows 2.0 VGA driver.


  1. TitovVN1974
    05.01.2023 21:49

    Вспоминаю что в разрешении 1600x1200 с 24 bit цветом Windows 3.11 работала на ATI Rage Pro128 (какой-то более старый драйвер от ATI подошел)