2. Uruchamianie urządzenia i renderowanie

Szczawik

1 Wstęp
2 Tworzenie urządzenia renderującego
3 Renderowanie
4 Utrata urządzenia
5 Całość kodu źródłowego klasy TDirectX

Wstęp

Wykorzystanie Direct3D wymaga utworzenia tak zwanego urządzenia renderującego. Nie jest to fizyczne urządzenie a obiekt, który odpowiada za komunikację z kartą graficzną. Warto tu rozgraniczyć trzy podstawowe typy urządzenia:

  • referencyjne - wirtualne urządzenie, renderowanie dokonywane jest programowo przez sterowniki z użyciem pamięci RAM; wykorzystywane przy uruchamianiu aplikacji na przykład na wirtualnej maszynie,
  • software'owe - fizyczne urządzenie bez wsparcia akceleracji sprzętowej, renderowanie dokonywane jest przez sterowniki z możliwością użycia pamięci urządzenia,
  • hardware'owe - fizyczne urządzenie renderujące z akceleracją sprzętową, renderowanie dokonywane jest przez urządzenie renderujące z możliwością użycia pamięci urządzenia.

Utworzenie urządzenia wymaga jedynie wypełnienia pól obiektu, opisującego parametry renderowania, i wykorzystania go to tworzenia obiektu.

Tworzenie urządzenia renderującego

W kroku 1. Przygotowanie aplikacji stworzyliśmy szkielet klasy obsługującej Direct3D. Zdefiniowano w nim dwa pola prywatne: device będzie obiektem urządzenia, natomiast parent jest oknem, które stanowi cel renderowania.

Rozbudujmy konstruktor tak, aby tworzył urządzenie:

        /// <summary>
        /// Konstruktor obiektu obsługującego DirectX
        /// </summary>
        /// <param name="parent">Okno, które będzie celem renderowania grafiki</param>
        /// <param name="windowed">Czy renderować w oknie (true) czy pełnoekranowo (false)?</param>
        public TDirectX(Control parent, bool windowed)
        {
                this.parent = parent;
                
                PresentParameters presentParameters = new PresentParameters();
                presentParameters.Windowed = windowed;
                presentParameters.SwapEffect = SwapEffect.Discard;
                if (!windowed)
                {
                	presentParameters.FullScreenRefreshRateInHz = 60;
                	presentParameters.BackBufferWidth = 800;
                	presentParameters.BackBufferHeight = 600;
                	presentParameters.BackBufferCount = 1;
                	presentParameters.BackBufferFormat = Format.X8R8G8B8;
                	presentParameters.PresentFlag = PresentFlag.None;
                }

                device=new Device(0, DeviceType.Hardware, parent, CreateFlags.HardwareVertexProcessing, presentParameters);
				
                Initialize();
        }

Po pierwsze, obiekt typu PresentParameters zostaje wypełniony kolejno informacją czy wykorzystać tryb pełnoekranowy, po drugie - co zrobić z zawartością bufora obrazu po podmienieniu klatek (działa tu mechanizm podobny do double buffering; tzw. back buffer swap chain). Jeśli wybrano tryb pełnoekranowy, należy określić również częstotliwość odświeżania ekranu, rozdzielczość poziomą i pionową, ilość buforów w łańcuchu renderowania, format buforów oraz tryb podmiany bufora. Po więcej informacji odsyłam do dokumentacji, dołączonej do SDK.

Gdy ustawienia są wypełnione, można utworzyć urządzenie (typ Direct3D: Device) odpowiadające domyślnej (pierwszy parametr równy 0) karcie graficznej jako urządzeniu hardware'owemu, podanemu oknu, z uwzględnieniem sprzętowego przetwarzania wierzchołków (wymagane minimalnie wsparcie w karcie dla technologii T&L lub nowszej; dla maszyny wirtualnej należy wybrać SoftwareVertexProcessing) i zgodnego w wcześniej wypełnionymi parametrami.

Renderowanie

Samą metodę renderującą naszego obiektu TDirectX uzupełnijmy w następujący sposób:

/// <summary>
/// Wykonanie renderowania
/// </summary>
/// <param name="time">Czas działania aplikacji</param>
public void Render(float time)
{
	device.Clear(ClearFlags.Target, Color.Blue, 1.0f, 0);
	device.BeginScene();
			
	//Miejsce na implementację renderowania konkretnych elementów sceny
			
	device.EndScene();
	device.Present();
}

Po pierwsze, na kolor niebieski czyścimy zawartość bufora klatki, do którego będziemy dokonywali renderowania (dwa ostatnie parametry dotyczą buforów głębokości i obrysu, które nie są przez nas obecnie wykorzystywane). Rozpoczynamy renderowanie, a gdy je zakończymy - dokonujemy podmiany klatek obrazu.

Utrata urządzenia

Aplikacja po dokonaniu tych zmian powinna się skompilować, ale jeszcze nie będzie działała poprawnie. Podczas minimalizacji aplikacji czy jej rozciągania, urządzenie graficzne będzie resetowane. Aby je zachować, należy napisać metodę obsługi tych zdarzeń, by przywrócić w nich odpowiednie obiekty sceny.

Po linii tworzącej urządzenie dodaj następujące przypisania obsługi zdarzeń:

device.DeviceLost     += new EventHandler(this.InvalidateDeviceObjects);
device.DeviceReset    += new EventHandler(this.RestoreDeviceObjects);
device.Disposing      += new EventHandler(this.DeleteDeviceObjects);
device.DeviceResizing += new CancelEventHandler(this.EnvironmentResizing);

Dodatkowo w klasie TDirectX dodaj ich definicje:

protected virtual void InvalidateDeviceObjects(object sender, EventArgs e)
{
}
		
protected virtual void RestoreDeviceObjects(object sender, EventArgs e)
{
}
		
protected virtual void DeleteDeviceObjects(object sender, EventArgs e)
{
}
		
protected virtual void EnvironmentResizing(object sender, CancelEventArgs e)
{
}

Całość kodu źródłowego klasy TDirectX

    public class TDirectX
    {
        private Device device;
        private Control parent;

        /// <summary>
        /// Konstruktor obiektu obsługującego DirectX
        /// </summary>
        /// <param name="parent">Okno, które będzie celem renderowania grafiki</param>
        /// <param name="windowed">Czy renderować w oknie (true) czy pełnoekranowo (false)?</param>
        public TDirectX(Control parent, bool windowed)
        {
            this.parent = parent;

            PresentParameters presentParameters = new PresentParameters();
            presentParameters.Windowed = windowed;
            presentParameters.SwapEffect = SwapEffect.Discard;
            if (!windowed)
            {
                presentParameters.FullScreenRefreshRateInHz = 60;
                presentParameters.BackBufferWidth = 800;
                presentParameters.BackBufferHeight = 600;
                presentParameters.BackBufferCount = 1;
                presentParameters.BackBufferFormat = Format.X8R8G8B8;
                presentParameters.PresentFlag = PresentFlag.None;
            }

            device = new Device(0, DeviceType.Hardware, parent, CreateFlags.HardwareVertexProcessing, presentParameters);
            device.DeviceLost += new EventHandler(this.InvalidateDeviceObjects);
            device.DeviceReset += new EventHandler(this.RestoreDeviceObjects);
            device.Disposing += new EventHandler(this.DeleteDeviceObjects);
            device.DeviceResizing += new CancelEventHandler(this.EnvironmentResizing);
            Initialize();
        }

        /// <summary>
        /// Tworzenie wszystkich elementów sceny
        /// </summary>
        private void Initialize()
        {
        }

        /// <summary>
        /// Wykonanie renderowania
        /// </summary>
        /// <param name="time">Czas działania aplikacji</param>
        public void Render(float time)
        {
            device.Clear(ClearFlags.Target, Color.Blue, 1.0f, 0);
            device.BeginScene();
            //Miejsce na implementację renderowania konkretnych elementów sceny
            device.EndScene();
            device.Present();
        }

        #region Obsługa zdarzeń
        /// <summary>
        /// Obsługa zdarzenia utraty dostępu do urządzenia
        /// </summary>
        protected virtual void InvalidateDeviceObjects(object sender, EventArgs e)
        {
        }

        /// <summary>
        /// Obsługa zdarzenia przywrócenia urządzenia
        /// </summary>
        protected virtual void RestoreDeviceObjects(object sender, EventArgs e)
        {
        }

        /// <summary>
        /// Obsługa zdarzenia usunięcia urządzenia
        /// </summary>
        protected virtual void DeleteDeviceObjects(object sender, EventArgs e)
        {
        }

        /// <summary>
        /// Obsługa zdarzenia zmiany wymiarów obrazu
        /// </summary>
        protected virtual void EnvironmentResizing(object sender, CancelEventArgs e)
        {
        }
        #endregion
    }

6 komentarzy

Źle mnie zrozumiałeś -> zmiana położenia jest właśnie moim zamiarem, a problemem jest właśnie zmienianie vertexów za każdym razem. Chciałbym coś w stylu "podajesz lewy górny róg obrazu, oraz ewentualnie wysokość + szerokość". Co do pełnoekranowego - chyba masz rację, założę temat na forum jak znajdę czas.

W celu wiekszych dyskusji, umieszczaj tematy na forum. Co do rysowania figur: jesli nie potrzebujesz czegos renderowac zawsze od nowa, pomysl o wyrenderowaniu czegos na jedna teksture (render to surface). Skoro zmienia polozenie, to widocznie robisz cos zle i nie jest to wina DX tylko Twojego kodu; renderowanie jest procesem przewidywalnym i za kazdym razem powinno wygladac tak samo. Co do ustawien urzadzenia, poszukaj na necie o autodetekcji.

I jeśli już jesteśmy przy directX- ie: Czy da się narysować prostokąt inaczej niż poprzez vertexy? Bo w moim programie zmienia on położenie (lekko) przy każdym odmalowaniu i brzydko wygląda takie zmienianie ich za każdym razem (choć nie zajmuje wiele mocy obliczeniowej - 0ms na 100 powtórzeń). Tzn mam zmienne X i Y które określają lewy górny róg obrazu, wielkość jest stała.

  1. jak?;
  2. Spróbowałem też z SoftwareVertexProcessing i działa tak samo;
  3. A kartę, jeśli już o tym mowa, rzeczywiście mam nienajnowszą...

Sprawdz czy twoja karta wspiera CreateFlags.HardwareVertexProcessing.

Ja cię... nie mogę :)
Podczas pracy w trybie okienkowym (windowed == true) wszystko działa dobrze, ale przy pełnoekranowym wyskakuje błąd (InvalidCall Exception przy tworzeniu device) Nie mam pomysłu :/
(oczywiście wszystko takie samo jak w kodzie Szczawika, w szczególności tyczy się to sekcji "tylko jeśli !windowed")