Tablica jednowymiarowa interpretowana jako tablica dwuwymiarowa

Tablica jednowymiarowa interpretowana jako tablica dwuwymiarowa
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

Mam bufor w postaci jednowymiarowej tablicy dynamicznej:

Kopiuj
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.

Kopiuj
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:

Kopiuj

//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:

Kopiuj
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).

edytowany 9x, ostatnio: Crow
CR
I w jaki sposób powyższy kod to narusza? Przecież BitMask to wskaźnik, który nie kopiuje danych. Taka maska przyspiesza działanie kodu, bo nie muszę dokonywać kosztownych konwersji ani przeliczania współrzędnych z 1 do 2 wymiarów i odwrotnie.
_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:2 miesiące
0
Crow napisał(a):

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

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


Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
CR
Przy rozdzielczości 1080p każdy rząd mieści po 1920 pikseli. Numeracja pierwszego rzędu - w ujęciu jednowymiarowym - zaczyna się od 0, a kończy na 1919. Oznacza to, że pierwszy piksel drugiego rzędu będzie mieć indeks 1920, co - w ujęciu dwuwymiarowym - byłoby odpowiednikiem BitMask[1][0] (trzeba pamiętać, że tablice w Pascalu jako pierwszą współrzędną podają indeks rzędu, a w drugim kolumny). Skoro jednak mówimy o BitMask[1][1] (czyli drugi rząd, druga kolumna), to jego odpowiednikiem jednowymiarowym będzie Bits[1921]. Podstaw sobie do wzoru: [1 * 1920 + 1] :).
_13th_Dragon
Ok, przemyśl to jeszcze raz może zamień wymiary na 3x4 i sobie rozrysuj.
CR
Wybacz, ale to ty sobie musisz przemyśleć jak to działa :]. Jak nie wierzysz, skleć na szybko kilka linijek kodu i sam zobacz...
_13th_Dragon
Dobra, jak wiesz nie będę udowadniać podstaw.
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

@_13th_Dragon: Specjalnie dla ciebie :).

Kopiuj
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? :].

edytowany 1x, ostatnio: Crow
_13th_Dragon
Wiesz że indeksacja tabel zaczyna się od 0? Pora czemukolwiek się nauczyć
CR
No tak, ale co z tego wynika? Jaki argument płynie z tego twierdzenia?
_13th_Dragon
Zasada OST - "one source of true", masz ją złamaną wielokrotnie.
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Tuchów
  • Postów:12164
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).


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 4x, ostatnio: flowCRANE
Zobacz pozostały 1 komentarz
flowCRANE
To zależy w czym problem widzi @_13th_Dragon. :P
_13th_Dragon
Jak wiesz że BitMask[1][1] jest odpowiednikiem Bits[1921] oraz że to wcale nie są bity - to rozumiesz dobrze, ale to nie wynikało z postu.
CR
Przepraszam bardzo, co zatem wynikało z postu? Przecież nigdzie nie napisałem, że nowy wiersz tablicy rozpocznie się od Bits[1921], tylko że komórka ta jest odpowiednikiem BitMask[1][1]. Ty nie dałeś temu wiary, próbując mi wcisnąć, że to powinno być Bits[1920] dodatkowo zarzucając mi, że nie rozumiem podstaw... A nazywa się to Bits, bo tak to określają w dokumentacji MSDN, więc dla wygody tak sobie zostawiłem.
_13th_Dragon
Nie dałem wiary temu że wiesz na temat indeksacji od zera, każdy kto o tym wie oraz o zasadzi OST zapisał by to inaczej.
CR
A jak to niby powinienem zapisać? Zawsze chętnie dowiem się czegoś nowego.
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

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

flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Tuchów
  • Postów:12164
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:

Kopiuj
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.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 3x, ostatnio: flowCRANE
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
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.

edytowany 1x, ostatnio: Crow
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Tuchów
  • Postów:12164
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:

Kopiuj
// 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:

Kopiuj
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ł.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 4x, ostatnio: flowCRANE
CR
Poproszę :).
CR
No ale te bufory się nie pokrywają... Wrzucę przykład graficzny.
flowCRANE
A dobra, poczekaj. Chyba wiem w czym problem.
_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:2 miesiące
0
Kopiuj
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


Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
Zobacz pozostałe 7 komentarzy
_13th_Dragon
Wszystko zależy od tego co chcesz na forum zrobić: A. pomóc największej ilości potrzebujących pomocy - wtedy zauważasz pierwszy problem i o tym piszesz, po czym czekasz na A.1 - dziękuję już działa; lub A.2 - poprawiłem ale wciąż nie działa. albo B: pomóc garstce (autocenzura) ... moment ale ja nie chcę pomagać (autocenzura) włącznie z tobą.
CR
Najpierw się mnie bezpodstawnie czepiasz (zarzucając mi brak rozumienia podstaw), a gdy się bronię (wykazując jednocześnie twój błąd) się obrażasz. Jak wolisz.
_13th_Dragon
Nie rozumiesz zasady OST = nie znasz podstaw, nie da się obronić.
CR
Tak, bo nie zastosowałem jej na forum, oszczędzając sobie pisania dodatkowych linijek kodu. Mów mi jeszcze...
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
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.

edytowany 3x, ostatnio: Crow
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Tuchów
  • Postów:12164
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ś”?


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 1x, ostatnio: flowCRANE
Zobacz pozostały 1 komentarz
flowCRANE
Chodzi mi o listę obsługiwanych rozdzielczości – czy masz ją hardkodowaną, czy pobierasz z systemu.
CR
Wiem, wiem, póki co mam zmienną Resolution_Render typu TPoint, do której odpowiednia wartość jest przypisywana przy starcie programu (chwilowo na podstawie stałej z innego modułu "developerskiego"). Docelowo jednak zmienna będzie zaciągana z pliku konfiguracyjnego, a ten będzie zależny od ustawień użytkownika. Wybór użytkownika będzie polegał na zaznaczeniu jednej opcji z listy, a ta będzie pobierana (przy starcie programu) z systemowej listy obsługiwanych rozdzielczości (jeszcze nie wiem jak, ale takie mam plany ;d).
flowCRANE
Hmm… IMO Twój problem nie ma rozwiązania, które obsłuzy wszystkie rozdzielczości i da Ci składnię znaną z macierzy dwuwymiarowych i które nie będzie oznaczało ciągłych obliczeń adresów w getterze/setterze. ;)
CR
Rozwiązanie jakieś pewnie znajdę, tylko raczej nieidealne. Będę musiał pomierzyć co jest najszybsze i na coś się zdecydować. Swoją drogą - może to mnie na coś naprowadzi - czemu dostęp do komórek tablicy-maski jest szybszy, niż dostęp do poszczególnych wskaźników, utworzonych dla poszczególnych komórek bufora 1D? W zasadzie to maska jest nawet szybsza (średnio o 2 ms) od bezpośredniej modyfikacji komórek w buforze 1D.
flowCRANE
Wszystko zależy od tego w jaki sposób przekazujesz i jak obrabiasz bufor jednowymiarowy. Bez kodu nic nie da się stwierdzić.
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

To

Kopiuj

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:

Kopiuj
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

Kopiuj
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; 
edytowany 6x, ostatnio: Crow
_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:2 miesiące
0

Szanowny Panie Szanowna Pani

Kopiuj
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ę:

Kopiuj
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.

Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
edytowany 3x, ostatnio: _13th_Dragon
CR
Pisałem przecież - prędkość. Ciągłe wywoływanie procedury pomocniczej, konwertującej współrzędne 2D na 1D, zabiera mi średnio ok. 2-3 milisekundy w skali całej klatki (ale może i więcej, wszystko zależy od złożoności sceny i ilości poligonów, które trzeba rasteryzować). Może się wydawać, że to niedużo, ale biorąc pod uwagę, że mam tylko 16 ms na wyrenderowanie i wyświetlenie gotowej klatki na ekranie, 2-3 ms robi ogromną różnicę...
_13th_Dragon
No to masz wyżej zmapowane na początku, raz a dobrze, przy czym w jedną albo w drugą stronę. Raz zmapowałeś działasz na 2D lub 1D. Tak a propos radziłbym RGB dodać nie jako strukturę zaś jako kolejny wymiar tablicy [0..2]
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

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

Kopiuj

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.

edytowany 2x, ostatnio: Crow
_13th_Dragon
Ale nie sprawdziłeś czy trafiasz w te adresy co chciałeś.
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Tuchów
  • Postów:12164
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.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
CR
A to w takim razie co by tłumaczyło wyraźną przewagę szybkości maski statycznej? A co do iterowania, to zgadza się, szybsze byłoby ustawienie sobie wskaźnika i przeskakiwanie Inc bez zaczytywania adresu za każdym razem, ale iterowanie to po prostu przykład, ja potrzebuję dostępu do pikseli, by móc rysować jak w Canvas.Pixels
CR
A gdybym faktycznie zastosował to rozwiązanie z listą masek dopasowanych do rozdzielczości, to jak miałbym rozpoznawać, której mam aktualnie używać? ifdla każdej maski z osobna i sprawdzać, czy warunek się wypełnił?
flowCRANE
Nie – zadeklarować dodatkową zmienną, w której przechowywana byłaby bieżąca maska. Po zmianie rozdzielczości wystarczyło by raz walnąć case of i do zmiennej wpisać nowy wskaźnik. Dzięki temu używałbyś jednej zmiennej we wszystkich procedurach, nie tracąc czasu na ify wykonywane za każdym razem.
flowCRANE
Kwestia tylko sprawdzenia czy składniowo da się to osiągnąć, bo zamysł jest w porządku.
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
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).

Kopiuj
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ć...

_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:2 miesiące
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;

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

Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
edytowany 1x, ostatnio: _13th_Dragon
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Tuchów
  • Postów:12164
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).


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
CR
Może być, tylko jak przedstawić dynamiczną tablicę dwuwymiarową, jako dynamiczną tablicę jednowymiarową? Z tego co widzę, wielowymiarowe dynamiczne tablice w Pascalu nie stanowią jednolitego ciągu bitów i to jest problem, bo nie da się ich łatwo interpretować.
flowCRANE
Jeśli użyjesz konstrukcji array of array of, to nie zadeklarujesz macierzy dwywymiarowej, a tablicę tablic, która faktycznie nie będzie stanowiła ciągłego bloku danych (a nawet nie będzie musiała być prostokątną).
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
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).

Kopiuj
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ć?

edytowany 1x, ostatnio: Crow
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Tuchów
  • Postów:12164
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.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 1x, ostatnio: flowCRANE
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
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ł.

Kopiuj

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.

edytowany 7x, ostatnio: Crow
Zobacz pozostałe 14 komentarzy
CR
Przecież tego nie robię... SetDIBits wypełnia od razu cały bufor bitmapy (HBITMAP), a nie bit po bicie, czy bajt po bajcie. Przypisywanie poszczególnych bitów jest mi natomiast potrzebne w procesie rasteryzacji poligonów (a dokładnie to trójkątów). Praktycznie każdy napisany przez siebie kawałek kodu testuję pod kątem szybkości i walczę dosłownie o każdą mikrosekundę, więc mam rozeznanie co działa szybciej, a co wolniej. Jeżeli się nie zgadzasz, to wykorzystaj moje algorytmy (albo napisz własne) i wrzuć proszę porównawcze wykresy prędkości, tak jak ja zrobiłem.
_13th_Dragon
ale najpierw wypełniasz bit po bicie, aby później raz skopiować
CR
Innego wyboru nie mam. Nie korzystam bibliotek wspierających rasteryzację (pozwalają jedynie na rysowanie linii i prostych prymitywów, ale to jest mi zupełnie zbędne, bo używam własnych algorytmów rasteryzacji i teksturowania, z uwzględnieniem perspektywy). Po skończeniu tego procesu piksele muszę przecież jakoś narysować na ekranie, a do tego potrzebuję bitmapy, którą muszę tymi pikselami najpierw wypełnić. Próbowałem CreateDIBSection, które pozwala modyfikować bufor bezpośrednio wewnątrz bitmapy, ale i tak działa to wolniej. Minimalnie, ale wolniej.
_13th_Dragon
Ok, rozumiem że nie chcesz nic sprawdzać, to po jakiego ch#ja zadajesz tu pytania skoro uważasz że w tym temacie wszystkie zęby zjadłeś? Może zgłoś się do jakieś uczelni wyższej aby ci nadano tytuł profesora? Doprawdy nie rozumiem po kiego Waść udziela się na tym "nędznym forum"?
CR
Dobra, ja już nie mam siły się z tobą użerać, szkoda mojego czasu i nerwów. Pozdrawiam.
_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:2 miesiące
0
Kopiuj
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;

Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
edytowany 1x, ostatnio: _13th_Dragon
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)