
И устраиваем "тотализатор": обработается ли всё до конца 11-й строки, или мы опять "выпадем из реального времени"?
Пора привести полный листинг программы, поскольку на ней "живого места не осталось" - прилично перелопатили. Зато теперь она компактная, всего лишь 81 слово!
main proc 00 FD57 SP Stack ;инициализация стека. ProcessFrame proc 01 A143 j 1 ;отныне и во веки веков! 02 2001 ACQ VSync ;"застревает" до тех пор, пока кадровый импульс не придёт 03 A249 k 9 04 ED79 Z D1 05 203A @@topRows: ACQ HSync 06 AA5F kLOOP @@topRows 07 8AE8 C [Z+k] ;ожидаемый диаметр точки, всё-таки штука весьма нужная, лучше держать локальной перем. 08 FE00 [SP+2j] 0 ;в [SP+2] храним номер строки. начнём всё-таки с максимума... 09 B049 JMP @@FinalRange ;чтобы СНАЧАЛА отправить запрос, а потом уже смотреть результат, так почему-то удобнее 0A FE80 @@newRow: [SP+2j] Acc 0B CD05 X ActivePoints ;начинаем цикл по всем активным точкам 0C F288 @@ActPointsStart: [SP+2j+1] GPUL ;яркость самой яркой точки на отрезке 0D F08A [SP+1] GPUH ;соотв. координата 0E 807E Acc Threshold ;поставили задом наперёд, чтобы избежать Hazard'а 0F 83F2 SUB [SP+2j+1] ;вычитаем порог, положит. значение свидетельствует о новом найденном "пятне" 10 B813 JL @@MidCycle ;пятна нет, не торопимся заказывать отрезок 11 EDCD Z X ;Чтобы в Merge всегда использовать Z, независимо от того, левая или правая точка оказались 12 A043 i 1 13 80F0 @@checkCycle: Acc [SP+1] 14 83EA SUB [Z+2j+k] 15 8480 ABS Acc 16 FC80 [SP] Acc ;модуль разности - это и будет новый "диаметр" точки, если сработает слияние 17 8F83 DIV2S C ;не будем мудрить пока что, всё "в лоб" 18 8FE0 DIV2S [Z+1] 19 B82D JL @@DoMerge 1A EDC8 Z [X+k] 1B A837 iLOOP @@checkCycle 1C DD65 Y Heap 1D F30A [SP++] @@AfterListOp ListOp proc 1E 8879 ZAcc RoundZero 1F EDD8 Z [Y+k] 20 83D8 SUB [Y+k] 21 D8E8 [Y+k] [Z+k] 22 BC61 JGE @@OutOfMem 23 E8C8 [Z+k] [X+k] 24 C8ED [X+k] Z 25 8957 NOP 0 26 B0FF JMP [--SP] 27 FFB8 @@OutOfMem: [--SP] Call(OutOfMemory) ;SP возвращается на место, но выпрыгиваем в другое место, на обработку ошибки ListOp endp 28 CDED @@AfterListOp: X Z ;для дальнейшей работы (после MidCycle)... Чтобы мы не взялись за только что добавленную точку! 29 E083 [Z+1] C ;гориз. размер точки (будет расти при слияниях, а поначалу D, т.к мы сюда включаем и ) 2A F336 [SP++] @@MidCycle ;после вызова AcqQueue возвращаемся AcqAndUpdate proc 2B A023 i 2 2C 8957 NOP 0 ;избежать Hazard, ведь i участвует в следующей строке 2D C6F4 @@updCycle: [X+2j+i] [SP+i] 2E A85F iLOOP @@updCycle 2F 80CA AcqNoUpd: Acc [X+2j+k] 30 8FC0 DIV2S [X+1] ;теперь в аккмуляторе у нас X - Ceil(D/2) 31 2080 ACQ Acc ;первый отрезок 32 82C0 ADD [X+1] ;а вот теперь X + Floor(D/2) 33 2080 ACQ Acc ;второй отрезок 34 B0FF JMP [--SP] AcqAndUpdate endp 35 E0FC @@DoMerge: [Z+1] [SP] ;пока так 36 DDCD @@MidCycle: Y X 37 CDC8 X [X+k] 38 8879 ZAcc RoundZero 39 83C8 SUB [X+k] 3A BC59 JGE @@FinalRange ;наткнулись на NULL, значит, все точки обработали... 3B F288 [SP+2j+1] GPUL ;яркость 3C 8088 Acc GPUL 3D 83CB SUB [X+4j+k] 3E F08A [SP+1] GPUH ;пока не забыли - сразу второе слово запрашиваем (коорд точки), чтобы не сбить весь FIFO 3F F319 [SP++] @@ActPointsStart ;нам по-любому придётся вернуться именно сюда! 40 B84B JL AcqAndUpdate ;отсюда "выпрыгнем" сразу на метку @@ActPointsStart 41 80C2 Acc [X+2j+1] ;координата точки (из списка) 42 8EC0 DIV2A [X+1] ;прибавить половинку её диаметра 43 83F0 SUB [SP+1] ;вычесть текущий номер строки 44 BC4B JGE AcqNoUpd ;здесь ОБЯЗАН быть именно JGE, т.к флаг знака является одним из "аргументов" для AcqQueue 45 CD45 X AllPoints 46 F3B4 CALL ListOp 47 CDDD X Y ;теперь [X] ссылается уже не на обработанную и удалённую точку, а на следующую, так что всё верно 48 B0FF JMP [--SP] ;ура... 49 207C @@FinalRange: ACQ WholeRow 4A 203A ACQ HSync 4B 80FE Acc [SP+2j] 4C 830A SUB ImgHeight 4D 824A ADD ImgHeightP1 4E B82F JL @@newRow ProcessFrame endp 4F 8957 OutOfMemory: NOP 0 50 B004 @@endless: JMP @@endless main endp
А остановились мы на том, как уже обработали первый отрезок, ничего там не нашли, убедились что точек ещё много - и запросили следующий отрезок:

На нём мы получили яркость 0x6BD. Из неё мы вычитаем яркость с прошлых строк, 0xBD9, и результат оказывается в пользу текущей строки. Также запрашиваем координату, 0x05, заносим в стек адрес @@ActPointsStart = 0x0C, и прыгаем в процедуру AcqAndUpdate, адрес 0x2B.
Там мы присваиваем i=2, пропускаем один такт (NOP), после чего:

обновляем параметры точки в списке: яркость 0x6BD, Y-координата 0x0B (т.е 11-я), X-координата 0x05.
После чего готовим задания для GPU: берём координату центра пятна, 0x05, вычитаем из неё диаметр (6), делёный на два.

Отправляем ACQ 0x02, как и в прошлый раз. Затем прибавляем диаметр, получается 8, и отправляем ACQ 0x08 - всё верно. Успешно возвращаемся из процедуры сразу в @@ActPointsStart (0x0C), где тут же запрашиваем у GPU следующий отрезок. Получаем яркость 0xFFF (нулевую) и координату 0.

После мучительного сравнения понимаем, что здесь ничего нет - и прыгаем в @@MidCycle. Там адрес текущей точки, 0x1F, отправляется в регистр Y, на всякий случай, который сейчас наконец-то представится! А в регистр X кладём адрес следующей точки, 0x15, и в аккумулятор - следующей за ней, 0x1A.
Раз это не NULL, то заканчивать строку ещё рано, и мы тут же запрашиваем очередной отрезок у GPU. Яркость 0xFFF (нулевая), координата 0. Сравниваем с яркостью нашей самой первой точки, 0x39A.

Заносим в стек адрес @@ActPointsStart, поскольку всё равно предстоит туда прыгнуть, хоть тушкой, хоть чучелом. Пропускаем вызов AcqAndUpdate, поскольку яркость "пошла на спад". Теперь смотрим, а не пора ли отправить эту точку "на покой", в список AllPoints. Чтобы это определить, берём её Y-координату (7), прибавляем половинку её диаметра (6), вычитаем текущую Y-координату (11=0x1B), и поскольку результат отрицательный, на AcqNoUpd мы тоже не прыгаем. Вместо этого в регистр X помещаем указатель AllPoints, 0x11, где пока что лежит Null, т.к список пустой. И вызываем процедуру ListOp!

Люблю процедуры, которые в один скриншот помещаются! Значение из [Y], 0x15, вычитается из обнулённого аккумулятора, убедиться что это не NULL, и оно же помещается в регистр Z.
Дальше [Y]=[Z]=0x1A, т.е предыдущая точка (которую мы совсем недавно обнаружили) будет дальше ссылаться не на текущую (левую верхнюю), а на следующую за ней (правую верхнюю).
В OutOfMem не перешли - и то радость.
Дальше [Z]=[X]=0x8000, т.е только что отцепленная точка теперь ссылается в NULL, указывая, что в списке пока всего один элемент.
И наконец, [X] = Z = 0x15, то есть указатель AllPoints, который раньше указывал в NULL, теперь указывает в эту точку - перецепка завершена, и мы с почётом возвращаемся из процедуры.
Наконец, присваиваем X = Y, то есть возвращаемся к предыдущей точке, поскольку текущая уже "удалена" из списка ActivePoints.

И сразу же запрашиваем очередной отрезок. И о чудо - там опять яркость 0xFFF (нулевая) и координата 0. Прыгаем в @@MidCycle, где переходим на следующую точку 0x1A.

Между прочим, это уже предпоследний отрезок на строке. Неплохо идём, ещё больше 100 тактов есть в запасе!
Сравнив яркость 0xFFF с ранее записанной 0x6BD, решаем точку не "обновлять". Вместо этого берём её Y-координату, 7, прибавляем её диаметр (6), делённый на 2, вычитаем текущую Y-координату (11), получаем отрицательное значение - и принимаем решение удалить и эту точку!

Адрес удаляемой точки, 0x1A, заносится в регистр Z и вычитается из аккумулятора, проверить на NULL.
Затем [Y]=[Z]=0x12, т.е предыдущая точка теперь будет указывать на "правую фиктивную точку".
[Z]=[X]=0x15, т.е удаляемая сейчас точка будет указывать на ранее удалённую, а та, в свою очередь, на NULL.
[X] =Z=0x1A, то есть указатель AllPoints указывает на только что "удалённую" точку. Всё отлично!

Вернулись из процедуры, поставили X=0x1F (ШО, ОПЯТЬ? Это самая первая точка на строке!). И переместились в начало цикла по активным точкам.
Там мы запрашиваем последний отрезок на строке! Он оказывается столь же скучным, яркость 0xFFF (нулевая) и координата 0. Но теперь сброс буфера до того, как мы его успеем обработать, УЖЕ НЕ ГРОЗИТ!.

Успешно прыгаем в @@MidCycle, где регистр X "перемещается" на следующую точку, 0x12, из аккумулятора вычитаем адрес точки, следующей за этой, 0x8000, тем самым обнаруживая что это NULL. Значит, мы всё обработали, осталось только выдать два последних задания на этой строке.
Переходим на метку @@FinalRange, где посылаем последние два задания ("до конца строки" и "синхроимпульс+back porch"). Они проходят очень быстро (каждый за такт), поскольку входной буфер теперь большой, а сейчас заданий стало очень мало. Пока сидит "текущее задание" на синхроимпульс, затем отрезки до 0x02 (поискать что-то слева от нашей точки), до 0x08 (уточнить координаты этой точки) и до 0x1F (до конца строки), и наконец на синхроимпульс на конце следующей строки, итого 5 штук.
Наконец, загружаем в аккумулятор номер строки, 0x0B,

Проверяем, не пора ли закругляться, прибавляем единичку, переходим в начало цикла по строкам, заносим номер строки в [SP+2] - и начинаем ждать первого отрезка новой строки.
Закончили досрочно! Ещё 27 тактов будет ожидание.