Tablica jednowymiarowa interpretowana jako tablica dwuwymiarowa

0

Mam bufor w postaci jednowymiarowej tablicy dynamicznej:

var
  Bits: array of RGBTriple;

Jest w takiej a nie innej formie, bo tego wymagają microsoftowe funkcje z bibliotek GDI (GetDIBits i SetDIBits). Praca z bitmapą w takiej postaci jest raczej mało praktyczna (zwłaszcza, gdy dokonuje się przekształceń wierzchołków 3D na punkty 2D, pasujących do współrzędnych ekranu), potrzebuję więc sposobu, by móc ten bufor interpretować jako tablicę dwuwymiarową. Celowo piszę interpretować, bo chcę uniknąć konwersji (ta jest czasochłonna, a mi zależy na szybkości).

Póki co wymyśliłem to tak, że mam wskaźnik do statycznej, dwuwymiarowej tablicy, który działa jak maska (alias) dla bufora.

type
  TBitMask = array[0..1079, 0..1919] of RGBTriple;

var
  Bits: array of RGBTriple;
  BitMask: ^TBitMask;

//procedura
  SetLength(Bits, 1920 * 1080);
  BitMask := @Bits[0];

No a potem mogę używać równorzędnie:


//procedura
  Bits[1921] := 14;
  BitMask[1, 1] := 14;

//oba powyższe dają ten sam efekt

Problem jest taki, że to działa tylko przy statycznym określeniu wielkości tablicy-maski, a ta docelowo ma się zmieniać w zależności od ustawień, więc consty odpadają... Ktoś ma pomysł jak (byle niezbyt kosztownie pod względem szybkości) to obejść?

Wiem, że dosyć łatwo można konwertować same współrzędne i np. zrobić funkcję pomocniczą, która pobierze współrzędne 2D (X i Y) i zwróci współrzędną w 1D:

function Get1D(const AX, AY, ABitmapWidth: Integer): Integer; inline;
begin
  Result := AY * ABitmapWidth + AX; 
end;

Niestety jest to spora strata szybkości (ok. 5 ms), gdy wywołuje się taką funkcję w pętli ponad 2 miliony razy. Potrzebuję czegoś szybszego. W ostateczności zrobię dynamiczną, dwuwymiarową tablicę wskaźników na RGBTriple i po prostu w pętli przypiszę każde pole tej tablicy do odpowiedniego adresu w buforze (niestety takie rozwiązanie zjada mi całe 2-3 ms).

0
Crow napisał(a):

Bits[1921] := 14;
BitMask[1, 1] := 14;

Jesteś tego pewien? Na pewno nie Bits[1920] := 14;

0

@_13th_Dragon: Specjalnie dla ciebie :).

type
  TMask = array[0..1079, 0..1919] of Integer;

var
  A1: array of Integer;
  A2: ^TMask;

initialization
  AllocConsole;
  SetLength(A1, 1920 * 1080);
  A1[1921] := 12345;
  A2 := @A1[0];

  WriteLn(A1[1921]);
  WriteLn(A2[1][1]);

Skompiluj sobie i sprawdź. Może pora powtórzyć podstawy? :].

2

@Crow: jeśli macierz indeksujesz od 0, to indeks ostatniej komórki ma wartość n-1. Tak więc jeśli dany wiersz ma 1920 komórek, to mają one indeksy od 0 do 1919. Jesli użyjesz indeksu 1920 w nieostatnim wierszu to odczytasz wartość pierwszej komórki kolejnego wiersza, a jeśli zrobisz to dla ostatniego wiersza, to odczytasz dane spoza bloku macierzy (błąd off by one).

W przykładzie który podałeś wyżej, macierz A1 ma 2073600 komórek reprezentowanych przez ciągły blok pamięci, więc zakres ich indeksów to przedział od 0 do 20736991. Dlatego odczytanie komórki o indeksie 1921 jest prawidłowe, bo nie powoduje wykroczenia poza zakres (range checking niczego nie wyłapie).

0

A odnośnie mojego głównego problemu, jakieś sugestie? Czy porobienie tych wskaźników na konkretne komórki to jedyna opcja?

1

Eee tam, możesz sobie zadeklarować dwuwymiarową macierz o dowolnych rozmiarach i używać wskaźnika na taką macierz, aby mieć składnię znaną ze zwykłych dwuwymiarowych tablic. Na przykład tak:

type
  TPixelsMap = array [Word, Word] of RGBTriple;
  PPixelsMap = ^TPixelsMap;

Rozmiar bieżącej klatki i tak musisz gdzieś przechowywać, więc jego użyj do poprawnego iterowania.

0

@furious programming: Ale przecież przy takiej konstrukcji tracę cały sens tej maski. Bo powiedźmy, że moja maska ma zakres [0..9999, 0..9999], z czego ja w danej chwili używam np. tylko 1080p, czyli [0..1079, 0..1919]. Załóżmy, że będę chciał narysować odcinek o współrzędnych [1][1] - [1][6]. Przy dokładnej masce zamalowałbym w pętli piksele od BitMask[1][1] do BitMask[1][6] i sprawa załatwiona, bo zmiany automatycznie zaszłyby także w buforze, oznaczając od Bits[1921] do Bits[1926]. Przy niedokładnej masce tak nie będzie, bo ona się nie pokrywa z buforem.

1
Crow napisał(a):

@furious programming: Ale przecież przy takiej konstrukcji tracę cały sens tej maski. Bo powiedźmy, że moja maska ma zakres [0..9999, 0..9999], z czego ja w danej chwili używam np. tylko 1080p, czyli [0..1079, 0..1919]. Załóżmy, że będę chciał narysować odcinek o współrzędnych [1][1] - [1][6].

Dlatego właśnie deklaruje się wskaźnik na taką macierz, aby nie alokować pamięci na zapas i jednocześnie aby móc traktować macierz jednowymiarową tak jak dwuwymiarową (lub jako cokolwiek innego).

Przy dokładnej masce zamalowałbym w pętli piksele od BitMask[1][1] do BitMask[1][6] i sprawa załatwiona, bo zmiany automatycznie zaszłyby także w buforze, oznaczając od Bits[1921] do Bits[1926]. Przy niedokładnej masce tak nie będzie, bo ona się nie pokrywa z bufforem.

No nie, dokładnie to samo uzyskasz za pomocą opisanego przeze mnie ”aliasu” o znacznie większym rozmiarze, tyle że zamiast korzystać z Low i High, wykorzystaj rozdzielczość klatki, którą i tak gdzieś musisz przechowywać.


Podstawa to typy danych oraz zmienne do przechowywania danych:

// typy danych opisujące bufor płaski i dwuwymiarowy

type
  TPixelsBuffer1D = array of RGBTriple;
  TPixelsBuffer2D = array [Word, Word] of RGBTriple;
  
// wskaźnik na dwuwymiarowy bufor ramki

type
  PPixelsBuffer2D = ^TPixelsBuffer2D;
  
// zmienne przechowujące bufor oraz wskaźnik na początek jego danych

var
  FrameBuffer: TPixelsBuffer1D;
  FrameBufferPtr: PPixelsBuffer2D;

// zmienne przechowujące bieżącą rozdzielczość klatki
// zainicjowane domyślną rozdzielczością

var
  FrameWidth: Integer = 1920;
  Frameheight: Integer = 1080;

I teraz jeśli będziesz chciał obrobić sobie bufor klatki korzystając ze składni znanej z macierzy dwuwymiarowych, używasz do tego celu zmiennej FrameBufferPtr, a iterowanie po pikselach bufora realizujesz na podstawie bieżącej rozdzielczości przechowywanej w zmiennych FrameWidth i FrameHeight:

var
  RowIndex, ColIndex: Integer;
begin
  // dla wszystkich wierszy
  for RowIndex := 0 to FrameHeight - 1 do
    // dla wszystkich pikseli wiersza
    for ColIndex := 0 to FrameWidth - 1 do
      // wyzeruj kanał alpha bieżącego piksela
      FrameBufferPtr^[ColIndex, RowIndex].R := 0;
end;

Jeśli zechcesz zmienić rozdzielczość bufora to zmieniasz wartości zmiennych FrameWidth i FrameHeight, a wszystkie pętle we wszystkich procedurach obrabiających piksele same się dostosują. W razie wykorzystania wielowątkowego obrabiania bufora ramki, aktualizację wartości tych zmiennych rzecz jasna należy synchronizować.

Kodu nie testowałem, ale ogólna zasada trzyma się kupy i nie ma prawa nie działać. Zarówno jednowymiarowe macierze dynamiczne, jak i dwuwymiarowe macierze o statycznym rozmiarze, zawsze zajmują jeden ciągły blok pamięci – dlatego ww. konstrukcja zapewnia taką kompatybilność.

Oczywiście Word w deklaracji dwuwymiarowej tablicy możesz wymienić na dowolną inną wartość, byle była większa niż największa obsługiwana przez Twoją grę rozdzielczość klatki. Ale do tego akurat typ Word wystarczy, bo jego zakres to 0..65535, a tak dużej klatki na pewno nie będziesz potrzebował.

0
const
  XSize=1920;
  YSize=1080;

type
  TBitMask = array[0..Ysize-1, 0..XSize-1] of RGBTriple;

var
  Bits: array of RGBTriple;
  BitMask: ^TBitMask;

//procedura
  SetLength(Bits, YSize * XSize);
  BitMask := @Bits[0];

//procedura
  Bits[1+XSize+1] := 14;
  BitMask[1, 1] := 14;

1920 i 1080 - w jednym miejscu, zmiana rozmiarów w jednym miejscu - One Source of Truth

0

@furious programming:

title

Czarna ramka symbolizuje całą maskę. Zielona obwódka to część maski, która jest aktualnie w użyciu. Czerwone kratki symbolizują bufor 1D w sposób, w jaki pokrywa się on z maską.

Rozpatrzmy przykład dla niebieskiej kratki. Jakie będą jej współrzędne w masce? Będzie to [1, 2]. Nie mogę jednak użyć przypisania poprzez Maska[1, 2] = wartość. Gdyby maska była wymiarowa (dopasowana do aktualnie używanej), wtedy współrzędnej [1, 2] odpowiadałaby 9 komórka w buforze 1D i to ona powinna zostać nadpisana. Jednak w obecnej masce (niewymiarowej), współrzędnej [1, 2] odpowiada 17 komórka w buforze 1D, czyli zupełnie błędnie.

1

Wiem o co chodzi – to o czym pisałem w Twoim przypadku faktycznie nie zadziała. :D

A rozdzielczości masz zdefiniowane z góry, czy ładujesz ich listę ”skądś”?

0

To


type
  TMask = array[0..1079, 0..1919] of RGBTriple;

var
  Mask: ^TMask;
  Buffer: array of RGBTriple;
  X, Y: Integer;
  
  SetLength(Buffer, 1920 * 1080);
  Mask := @Buffer[0];

  for Y := 0 to 1079 do
    for X := 0 to 1919 do
      begin
        Mask[Y, X].rgbtRed := 255;
        Mask[Y, X].rgbtGreen := 0;
        Mask[Y, X].rgbtBlue := 0;
      end;

jest szybsze, niż to:

type
  TMask = array of array of ^RGBTriple;

var
  Mask: TMask;
  Buffer: array of RGBTriple;
  X, Y, I: Integer;

  for Y := 0 to 1079 do
    for X := 0 to 1919 do
      begin
        Mask[Y, X].rgbtRed := 255;
        Mask[Y, X].rgbtGreen := 0;
        Mask[Y, X].rgbtBlue := 0;
      end;
  
//oczywiście wcześniej maska została alokowana przy pomocy `SetLength`, a jej komórki zostały w pętli przypisane do kolejnych komórek Buffera.

  SetLength(Mask, 1080);
  I := 0;
  for Y := 0 to 1079 do
    begin
      SetLength(Mask[Y], 1920);
      for X := 0 to 1919 do
        begin
          Mask[Y][X] := @Buffer[I];
          inc(I, 1);
        end; 
    end;

lub nawet to

var
  Buffer: array of RGBTriple;
  I: Integer;

SetLength(Buffer, 1920 * 1080);

for I := 0 to (1920 * 1080) - 1 do 
  begin
    Buffer[I].rgbtRed := 255;
    Buffer[I].rgbtGreen := 0;
    Buffer[I].rgbtBlue := 0;
  end; 
0

Szanowny Panie Szanowna Pani

program ideone;

uses SysUtils;

//const YSize=1080;
//const XSize=1920;
const YSize=3;
const XSize=4;
type RGBTriple=record R,G,B:Byte; end;
type TBitMask=array[0..YSize-1,0..XSize-1] of RGBTriple;
type PBitMask=^TBitMask;
type TBits=array[0..YSize*XSize-1] of RGBTriple;
type PBits=^TBits;
function XY2Idx(X,Y:Integer):Integer; begin XY2Idx:=Y*XSize+X; end;

procedure test(const Bits:TBits;const BitMask:TBitMask);
var R:TTestRecord;
var Y,X,Idx:Integer;
begin
  for Y:=0 to YSize-1 do
  begin
    for X:=0 to XSize-1 do
    begin
      Idx:=XY2Idx(X,Y);
      WriteLn('Y:=',Y,'; X:=',X,'; Idx:=',Idx,' ',Int64(@Bits[Idx]),' - ',Int64(@BitMask[Y,X]));
    end;
  end;
end;

procedure go1;
var Bits:PBits;
var BitMask:TBitMask;
begin
  Bits:=@BitMask[0,0];
  test(Bits^,BitMask);
end;

procedure go2;
var Bits:TBits;
var BitMask:PBitMask;
begin
  BitMask:=@Bits[0];
  test(Bits,BitMask^);
end;

procedure go0;
var Bits:TBits;
var BitMask:TBitMask;
begin
  WriteLn(SizeOf(Bits),' => ',SizeOf(BitMask));
end;

begin
  go0;
  WriteLn;
  go1;
  WriteLn;
  go2;
end.

https://ideone.com/wYN4z5
Nie rozumiem co tu ci się nie zgadza?

Poza tym zawsze możesz użyć taką strukturę:

program ideone;
{$modeswitch result+}

//const YSize=1080;
//const XSize=1920;
const YSize=1080;
const XSize=1920;
type TBmp2D=array[0..YSize*XSize-1,0..2] of Byte;
type TBmp3D=array[0..YSize-1,0..XSize-1,0..2] of Byte;

type TMultiDim=record
  case Boolean of  
	false: (bmp2d:TBmp2D);
    true : (bmp3d:TBmp3D);
end;

var data:TMultiDim;

function XY2Idx(X,Y:Longint):Longint; 
begin
  XY2Idx:=XSize;
  XY2Idx:=Y*Result+X;
end;

procedure test(const data:TMultiDim);
var Y,X,Idx:Longint;
var ptr2d,ptr3d:Int64;
begin
  for Y:=0 to YSize-1 do
  begin
    for X:=0 to XSize-1 do
    begin
      Idx:=XY2Idx(X,Y);
      ptr2d:=Int64(@data.bmp2d[Idx,0]);
      ptr3d:=Int64(@data.bmp3d[Y,X,0]);
      if ptr2d<>ptr3d then WriteLn('BAD!!! Y:=',Y,'; X:=',X,'; Idx:=',Idx,';');
    end;
  end;
end;

begin
  FillByte(data,SizeOf(data),0);
  WriteLn(SizeOf(TBmp2D));
  WriteLn(SizeOf(TBmp3D));
  WriteLn(SizeOf(TMultiDim));
  test(data);
  WriteLn('Done');
end.
0

Dobra, skleciłem większy kod porównujący szybkość różnych algorytmów (przy okazji proszę o ocenę zastosowanej przeze mnie metodologii).


type
  TStaticMask = array[0..1079, 0..1919] of RGBTriple;

var //zmienne globalne (tak, wiem, nie powinno się, ale to tylko dla testów).
  CPU, Before, After: Int64;
  Bits: array of RGBTriple;
  StaticMask: ^TStaticMask;
  PointerMask: array of array of ^RGBTriple;
  I, Index: Integer;
  Output, Sum: Single; 

const
  Runs = 15;

{Fill Bits}

procedure FillBits;
var
  I: Integer;
begin
  QueryPerformanceCounter(Before);
  for I := 0 to (1920 * 1080) - 1 do
    begin
      Bits[I].rgbtRed := 250;
      Bits[I].rgbtGreen := 150;
      Bits[I].rgbtBlue := 50;
    end;
  QueryPerformanceCounter(After);
  Output := ((After - Before) * 1000) / CPU;
  WriteLn('FillBits: ',(Output):0:2, ' ms');
end;

{Fill Bits Converted Func}

function Get1D(const AX, AY, AWidth: Integer): Integer; inline;
begin
  Result := (AY * AWidth) + AX;
end;

procedure FillBitsConvertedFunc;
var
  X, Y: Integer;
begin
  QueryPerformanceCounter(Before);
  for Y := 0 to 1079 do
    for X := 0 to 1919 do
      begin
        Bits[Get1D(X, Y, 1920)].rgbtRed := 250;
        Bits[Get1D(X, Y, 1920)].rgbtGreen := 150;
        Bits[Get1D(X, Y, 1920)].rgbtBlue := 50;
      end;
  QueryPerformanceCounter(After);
  Output := ((After - Before) * 1000) / CPU;
  WriteLn('FillBitsConvertedFunc: ',(Output):0:2, ' ms');
end;

{Fill Bits Converted Proc}

procedure Set1D(const AX, AY, AWidth: Integer); inline;
begin
  Index := (AY * AWidth) + AX;
end;

procedure FillBitsConvertedProc;
var
  X, Y: Integer;
begin
  QueryPerformanceCounter(Before);
  for Y := 0 to 1079 do
    for X := 0 to 1919 do
      begin
        Set1D(X, Y, 1920);
        Bits[Index].rgbtRed := 250;
        Bits[Index].rgbtGreen := 150;
        Bits[Index].rgbtBlue := 50;
      end;
  QueryPerformanceCounter(After);
  Output := ((After - Before) * 1000) / CPU;
  WriteLn('FillBitsConvertedProc: ',(Output):0:2, ' ms');
end;

{Fill Static Mask}

procedure FillStaticMask;
var
  X, Y: Integer;
begin
  QueryPerformanceCounter(Before);
  for Y := 0 to 1079 do
    for X := 0 to 1919 do
      begin
        StaticMask[Y][X].rgbtRed := 250;
        StaticMask[Y][X].rgbtGreen := 150;
        StaticMask[Y][X].rgbtBlue := 50;
      end;
  QueryPerformanceCounter(After);
  Output := ((After - Before) * 1000) / CPU;
  WriteLn('FillStaticMask: ',(Output):0:2, ' ms');
end;

{Fill Pointer Mask}

procedure AttachPointerMask;
var
  X, Y, I: Integer;
begin
  I := 0;
  SetLength(PointerMask, 1080);
  for Y := 0 to 1079 do
    begin
      SetLength(PointerMask[Y], 1920);
      for X := 0 to 1919 do
        begin
          PointerMask[Y][X] := @Bits[I];
          Inc(I, 1);
        end;
    end;
end;

procedure FillPointerMask;
var
  X, Y: Integer;
begin
  QueryPerformanceCounter(Before);
  for Y := 0 to 1079 do
    for X := 0 to 1919 do
      begin
        PointerMask[Y][X].rgbtRed := 250;
        PointerMask[Y][X].rgbtGreen := 150;
        PointerMask[Y][X].rgbtBlue := 50;
      end;
  QueryPerformanceCounter(After);
  Output := ((After - Before) * 1000) / CPU;
  WriteLn('FillPointerMask: ',(Output):0:2, ' ms');
end;

initialization
  QueryPerformanceFrequency(CPU); //pobiera częstotliwość taktowania procesora

  SetLength(Bits, 1920 * 1080); //tworzy bufor z pikselami.
  StaticMask := @Bits[0]; //"Nakłada" statyczną maskę na bufor.
  AttachPointerMask; //Ustawia indywidualne wskaźniki na indywidualne komórki bufora

  Sum := 0;
  for I := 0 to Runs - 1 do
    begin
      FillBits;
      Sum := Sum + Output;
    end;
  WriteLn('On average: ', (Sum / Runs):0:2);
  WriteLn(' ');

  Sum := 0;
  for I := 0 to Runs - 1 do
    begin
      FillBitsConvertedFunc;
      Sum := Sum + Output;
    end;
  WriteLn('On average: ', (Sum / Runs):0:2);
  WriteLn(' ');

  Sum := 0;
  for I := 0 to Runs - 1 do
    begin
      FillBitsConvertedProc;
      Sum := Sum + Output;
    end;
  WriteLn('On average: ', (Sum / Runs):0:2);
  WriteLn(' ');

  Sum := 0;
  for I := 0 to Runs - 1 do
    begin
      FillStaticMask;
      Sum := Sum + Output;
    end;
  WriteLn('On average: ', (Sum / Runs):0:2);
  WriteLn(' ');

  Sum := 0;
  for I := 0 to Runs - 1 do
    begin
      FillPointerMask;
      Sum := Sum + Output;
    end;
  WriteLn('On average: ', (Sum / Runs):0:2);

Wyniki:

title

To oczywiście tylko malutka próbka kontrolna (15 powtórzeń, żeby screen z wynikami za duży nie był ;]), ale przepuszczałem też po 50, 200 i 1000 razy, a tendencja jest zawsze ta sama (to znaczy hierarchia prędkości algorytmów się nie zmienia). No i muszę przyznać, że to co otrzymałem, jest dla mnie trochę zaskakujące. Rozumiem, że procedura pomocnicza Set1D jest szybsza od funkcji pomocniczej Get1D bo jednak brak kopiowania danych robi swoje, ale czemu np. modyfikacja bezpośrednio w buforze jest szybsza przy użyciu podwójnej pętli (i to jeszcze z wywołaniem procedury lub funkcji pomocniczej, co prawda z inline, ale zawsze), od modyfikacji dokonanej w pętli jednowymiarowej? A to że maska statyczna wygrywa ze wszystkimi - jeżeli dobrze kombinuję - jest chyba związane z faktem, że Delphi (Pascal?) wolniej przetwarza tablice dynamiczne niż statyczne...? No i czemu wskaźniki na poszczególne komórki bufora są takie wolne?!

Jeżeli nie zostanie mi nic innego, to chyba zdecyduję się na konwersje współrzędnych przy pomocy procedury pomocniczej, bo to drugie, najszybsze rozwiązanie, które jednocześnie pozwoli mi bez przeszkód manipulować wymiarami bufora.

2
Crow napisał(a):

[…] ale czemu np. modyfikacja bezpośrednio w buforze jest szybsza przy użyciu podwójnej pętli (i to jeszcze z wywołaniem procedury lub funkcji pomocniczej, co prawda z inline, ale zawsze), od modyfikacji dokonanej w pętli jednowymiarowej?

Zobacz do assemblera – tam znajdziesz odpowiedź. ;)

A to że maska statyczna wygrywa ze wszystkimi - jeżeli dobrze kombinuję - jest chyba związane z faktem, że Delphi (Pascal?) wolniej przetwarza tablice dynamiczne niż statyczne...?

Nie rozumiem dlaczego by tak miało być. Jedno i drugie to blok pamięci o zadanym rozmiarze i o ile nie zmienia się rozmiaru macierzy wewnątrz danego algorytmu (np. pętli modyfikujących wartości komórek) to w obu przypadkach złożoność powinna być taka sama.

No i czemu wskaźniki na poszczególne komórki bufora są takie wolne?!

Bo wybrałeś najgorszy możliwy sposób dostępu do komórek, jaki tylko się da. W każdej pętli zamiast operować bezpośrednio na bloku danych, w każdej iteracji najpierw pobierany adres bajtu do modyfikacji, a dopiero później na podstawie tego adresu modyfikuje się dane. I tak trzy razy dla każdej iteracji.

Jeśli chcesz szybko przeiterować po pikselach to użyj jednego wskaźnika i iteruj wskaźnik o rozmiar piksela (czyli u Ciebie o 3 bajty). Natomiast jeśli potrzebujesz wypełnić dany obszar zadanym kolorem, to nie ustawiaj każdej wartości z osobna, a zmontuj sobie jednego inta z trzech bajtów i jego przypisuj do bieżącego piksela. Tym bardziej tak zrób, jeśli optymalizator sam nie upraszcza potrójnego przypisania.

Jeżeli nie zostanie mi nic innego, to chyba zdecyduję się na konwersje współrzędnych przy pomocy procedury pomocniczej, bo to drugie, najszybsze rozwiązanie, które jednocześnie pozwoli mi bez przeszkód manipulować wymiarami bufora.

Jest jeszcze inny sposób – po to pytałem, czy rozdzielczości masz hardkodowane czy nie. Możesz sobie dla każdej obsługiwanej rozdzielczości zadeklarować osobny wskaźnik na macierz o statycznym rozmiarze i wykorzystywać w zależności od bieżącej rozdzielczości klatki.

0

@furious programming: No właśnie nie wiem, czy to się da ogarnąć (a przynajmniej póki co nic mi nie przychodzi do głowy).

type
  TMaskA = array[0..1079, 0..1919] of RGBTriple;
  TMaskB = array[0..799, 0..599] of RGBTriple;

var
  MaskA: ^TMaskA;
  MaskB: ^TMaskB;

No i co dalej? Jak zrobić zmienną, która raz pomieści ^TMaskA, a raz ^TMaskB? Myślałem o jakichś type castach, ale jakoś nie mogę wykombinować...

0

Jak masz kilka zdefiniowanych rozmiarów - to wiadomo.
Jak możesz mieć każdy z nich to nie da rady.

Przetestuj jeszcze ten rozkład, oraz kopiowanie 3-ch bajtów z jednego inta;

I:=0;
for Y:=0 to YSIze-1 do
begin
  for X:=0 to XSIze-1 do
  begin
    TB[I]:= ...
    Inc(I);
  end;
end;
1

@Crow: chyba nici z tego będą. Co innego gdybyś miał sytuację odwrotną i potrzebował traktować dowolną dwuwymiarową macierz jako jednowymiarową – wtedy dało by się to załatwić prostym rzutowaniem, absolute czy jeszcze czymś innym.

Ale pomyślę jeszcze nad jakimś hackiem, tak aby to załatwić samymi typami danych (bez narzutu).

0

Wykombinowałem w sumie dosyć proste rozwiązanie, to znaczy stworzyłem procedurę pomocniczą, która od razu dokonuje zmian w buforze (zamiast dokonywać jedynie konwersji współrzędnych).

procedure SetPixel(const AX, AY, AWidth: Integer; const AR, AG, AB: Byte); inline;
var
  Index: Integer;
begin
  Index := (AY * AWidth) + AX;
  Bits[Index].rgbtRed := AR;
  Bits[Index].rgbtGreen := AG;
  Bits[Index].rgbtBlue := AB;
end;

procedure FillDirect;
var
  X, Y: Integer;
begin
  QueryPerformanceCounter(Before);
  for Y := 0 to 1079 do
    for X := 0 to 1919 do
      SetPixel(X, Y, 1920, 250, 150, 50);
  QueryPerformanceCounter(After);
  Output := ((After - Before) * 1000) / CPU;
  WriteLn('FillDirect: ',(Output):0:2, ' ms');
end;

Działa to prawie tak samo szybko, jak maska statyczna (plus minus 0.03 ms) więc w zasadzie mam co chciałem :). Zastanawia mnie tylko jedna rzecz. W procedurze pomocniczej SetPixel podaję szerokość bitmapy (AWidth), choć w zasadzie nie jest to konieczne (bo po co, skoro mógłbym wczytywać wielkość ze zmiennej?). Sęk w tym, że próba zastąpienia parametru odczytem ze zmiennej (a nawet stałej, bo tego też próbowałem) negatywnie odbija się na szybkości i nie jest to bynajmniej różnica rzędu 0.03 - 0.05 ms, tylko ok. 0.50 - 1.00 ms, a więc sporo. Czy ma to coś wspólnego z dostępem do pamięci? Da się coś z tym zrobić?

1
Crow napisał(a):

Działa to prawie tak samo szybko, jak maska statyczna (plus minus 0.03 ms) więc w zasadzie mam co chciałem :).

Inaczej tego nie zrobisz – kombinowaliśmy, ale nic z tego nie wyszło. Ze względu na ograniczenia języka i silne typowanie, nie ma możliwości aby załatwić sprawę inaczej niż przeliczeniami współrzędnych na indeks komórki.

Sęk w tym, że próba zastąpienia parametru odczytem ze zmiennej (a nawet stałej, bo tego też próbowałem) negatywnie odbija się na szybkości […] Czy ma to coś wspólnego z dostępem do pamięci?

Oczywiście – w Twoim przykładzie podajesz literał w tym parametrze, co zapewne zostaje zoptymalizowane. Nie ma pobierania wartości z pamięci, a używana jest zawsze taka sama, hardkodowana wartość. Dlatego przy podaniu zmiennej czas się wydłuża.

Da się coś z tym zrobić?

Skoro rozmiar klatki nie jest znany w trakcie kompilacji, to siłą rzeczy musisz korzystać ze zmiennej przechowującej bieżące rozmiary, więc – nie za bardzo.

0

Udało mi się uzyskać znaczną poprawę wydajności (rzędu ok. 2 milisekund, czyli w moim przypadku jakieś 45%), więc wrzucę dla potomnych, gdyby ktoś, kiedyś szukał.


type
  TColorByte = record
    case Byte of
      0: (Color: Integer);
      1: (B, G, R: Byte);
    end;

var
  Bits: array of TColorByte;

procedure SetPixelFast(const AX, AY, AWidth: Integer; const AR, AG, AB: Byte); inline;
var
  Index: Integer;
begin
  Index := (AY * AWidth) + AX;
  Bits[Index].Color := (AR shl 16) or (AG shl 8) or AB;
end;

Kilka uwag:

  1. Obliczanie indeksu bajtu, który ma zostać nadpisany, odbywa się na raty (najpierw współrzędna zostaje zapisana do zmiennej Index), bo tak jest po prostu szybciej (różnica nawet 1 ms). Interface tablic w Delphi chyba tak lubi ;d.
  2. Ustawienie bajtów w TColorByte może się różnić (VCL i GDI używają ich odwrotnie), więc gdyby były problemy, warto spróbować przestawić miejscami R i B (zarówno w rekordzie TColorByte, jak i w procedurze SetPixelFast).
  3. Powyższy sposób jest dostosowany do kolorów 32 bitowych (4 bajty) i nie zadziała (bez modyfikacji) z innymi formatami.
  4. To bardziej ogólna wskazówka, ale też przydatna. Należy pamiętać, żeby ciało procedury czy funkcji opatrzonej dyrektywą inline znajdowało się zawsze przed ciałem funkcji czy procedury, która ją wywołuje. Inaczej kompilator nie zastosuje dyrektywy, a różnica w szybkości będzie kolosalna (u mnie ok. 5 ms).

Z dokumentacji Embarcadero:

Within a unit, the body for an inline function should be defined before calls to the function are made. Otherwise, the body of the function, which is not known to the compiler when it reaches the call site, cannot be expanded inline.

0
procedure Gradient(bmpWidth,bmpHeight:Integer;UpB,UpG,UpR,DnB,DnG,DnR:Byte);
type TRgbPixel=packed record B,G,R:Byte; end;
type TRgbPixelColor=packed record
  case Boolean of
    true:  (px:TRgbPixel);
    false: (clr:TColor);
end;
type TRgbPixelRow=array[0..((MAXINT-1) div SizeOf(TRgbPixel))-1]of TRgbPixel;
type PRgbPixelRow=^TRgbPixelRow;
type TRgbMatrix=array of PRgbPixelRow;
var Y,X:Integer;
var ClrToPx:TRgbPixelColor;
var Arr:TRgbMatrix;
var bmp:TBitmap;
begin
  SetLength(Arr,bmpHeight);
  bmp:=TBitmap.Create;
  try
    bmp.PixelFormat:=pf24bit;
    bmp.Width:=bmpWidth;
    bmp.Height:=bmpHeight;
    for Y:=0 to bmpHeight-1 do Arr[Y]:=PRgbPixelRow(Bmp.ScanLine[Y]);
    for Y:=0 to bmpHeight-1 do
    begin
      ClrToPx.clr:=RGBToColor
      (
        (UpB*(bmpHeight-1-Y)+DnB*Y) div (bmpHeight-1),
        (UpG*(bmpHeight-1-Y)+DnG*Y) div (bmpHeight-1),
        (UpR*(bmpHeight-1-Y)+DnR*Y) div (bmpHeight-1)
      );
      for X:=0 to bmpWidth-1 do
      begin
        Arr[Y]^[X]:=ClrToPx.px;
      end;
    end;
    Bmp.SaveToFile('C:\APP\Test\PixelTestOut.bmp');
  finally
    bmp.Free;
  end;
end;

1 użytkowników online, w tym zalogowanych: 0, gości: 1