Emulare GameBoy Advance

Pubblicità
Stato
Discussione chiusa ad ulteriori risposte.
@DispatchCode thanks, ho gia avuto abbastanza problemi cercando di capire come dovevo intrerpretare l'istruzione in base all' endianness (ARM può operare sia in small endian che big endian da quel che ho capito) perchè non mi era chiaro neppure che rappresentazione fosse usata nel grafico nella prima immagine, un pò deludente il fatto che ho finito per usare un disassembler gia funzionante come reference e modificato il modo in cui interpretavo istruzioni fino a che lo stesso input non mi ha dato l'output in quel sito lì, percui ho deciso che valeva la pena spendere un pò di tempo in più ed assicurarmi di capire davvero cosa sta succedendo, visto che riesco a ragionare molto meglio con tutte le info a portata di mano.
Devo ancora aggiungere il display di "CPSR" (registro con tutte le flag dello stato corrente del processore) ed in alto a destra magari in futuro una stack di esecuzione delle istruzioni.

@driverfury Windows, Direct3D, Direct2D, DirectWrite, sotto la classe base dalla quale derivano le mie finestre, tutta boilerplate:
C++:
#include "GBAEmulator_PCH.h"
#include "D2DWindowBase.h"
#include "GBAEmulator.h"

D2DWindowBase::D2DWindowBase(LONG width, LONG height, HINSTANCE hInstance, GBAEmulator* emuInstance)
    : m_isValid{ false }
    , m_winWidth{ width }
    , m_winHeight{ height }
    , m_background(0.047f, 0.062f, 0.129f, 1.f)
    , m_hInstance{ hInstance }
    , m_emu{ emuInstance }
    , m_hwnd{ nullptr }
    , m_device{ nullptr }
    , m_context{ nullptr }
    , m_swapChain{ nullptr }
    , m_backBuffer{ nullptr }
{
}

D2DWindowBase::~D2DWindowBase()
{
    m_isValid = false;
    if (m_DWriteFactory) {
        m_DWriteFactory->Release();
    }
    if (m_device) {
        m_device->Release();
    }
    if (m_context) {
        m_context->Release();
    }
    if (m_swapChain) {
        m_swapChain->Release();
    }
    if (m_backBuffer) {
        m_backBuffer->Release();
    }
}

bool D2DWindowBase::IsValid() const
{
    return m_isValid;
}

LONG D2DWindowBase::Width() const
{
    return m_winWidth;
}

LONG D2DWindowBase::Height() const
{
    return m_winHeight;
}

HWND D2DWindowBase::Hwnd() const
{
    return m_hwnd;
}

//This method shouldn't rely on the m_emu variable because
//not all emu member variables are ensured to be initialized at this point.
bool D2DWindowBase::InitWindow(const std::string& windowCaptionText, const WNDCLASSEX& wndClass, DWORD windowStyle, bool bMenu)
{
    //Register Class
    //The check below prevents trying to register a previously registered class again
    WNDCLASSEX temp{};
    if (!GetClassInfoEx(m_hInstance, wndClass.lpszClassName, &temp)) {

        if (!RegisterClassEx(&wndClass)) {
            MessageBox(nullptr, "Failed to register window class", "Error", MB_OK);
            return false;
        }
    }

    //Create Window

    DWORD windowExStyle = 0;

    RECT client{ 0, 0, m_winWidth, m_winHeight };

    SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
    UINT dpi = GetDpiForSystem();

    AdjustWindowRectExForDpi(&client, windowStyle, bMenu, windowExStyle, dpi);

    m_winWidth = client.right - client.left;
    m_winHeight = client.bottom - client.top;

    int x = GetSystemMetrics(SM_CXSCREEN);
    int y = GetSystemMetrics(SM_CYSCREEN);
    x = x / 2 - m_winWidth / 2;
    y = y / 2 - m_winHeight / 2;

    m_hwnd = CreateWindowEx(windowExStyle,
        wndClass.lpszClassName,
        windowCaptionText.c_str(),
        windowStyle,
        x, y, m_winWidth, m_winHeight,
        nullptr,
        nullptr,
        m_hInstance,
        m_emu);

    if (!m_hwnd) {
        MessageBox(nullptr, "Failed to create window", "Error", MB_OK);
        return false;
    }

    //Init Direct3D

    HRESULT hr;
    ID3D11Device* d3d_device;
    ID3D11DeviceContext* d3d_context;

    UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

    const UINT featureCount = 7;
    D3D_FEATURE_LEVEL featureLevels[featureCount] = {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1
    };

    hr = D3D11CreateDevice(nullptr,
        D3D_DRIVER_TYPE_HARDWARE,
        nullptr,
        creationFlags,
        featureLevels,
        featureCount,
        D3D11_SDK_VERSION,
        &d3d_device,
        nullptr,
        &d3d_context);
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to initialize D3D", "Error", MB_OK);
        return false;
    }

    //Init Direct2D

    IDXGIDevice* dxgiDevice;
    d3d_device->QueryInterface(&dxgiDevice);

    D2D1_CREATION_PROPERTIES D2D1DeviceDesc{};
    D2D1DeviceDesc.threadingMode = D2D1_THREADING_MODE_SINGLE_THREADED;
    D2D1DeviceDesc.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;

    hr = D2D1CreateDevice(dxgiDevice, D2D1DeviceDesc, &m_device);
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to create D2D1 Device", "Error", MB_OK);
        return false;
    }

    hr = m_device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &m_context);
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to create D2D1 Context", "Error", MB_OK);
        return false;
    }

    //Create Swap Chain
    IDXGIAdapter* dxgiAdapter = nullptr;
    hr = dxgiDevice->GetAdapter(&dxgiAdapter);
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to get Adapter", "Error", MB_OK);
        return false;
    }

    IDXGIFactory2* dxgiFactory = nullptr;
    hr = dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to get dxgiFactory", "Error", MB_OK);
        return false;
    }

    //swap chain desc
    DXGI_SWAP_CHAIN_DESC1 swapChainDesc{};
    swapChainDesc.Width = m_winWidth;
    swapChainDesc.Height = m_winHeight;
    swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    swapChainDesc.Scaling = DXGI_SCALING_NONE;
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.SampleDesc.Quality = 0;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
    swapChainDesc.BufferCount = 2;
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;

    dxgiFactory->CreateSwapChainForHwnd(d3d_device, m_hwnd, &swapChainDesc, nullptr, nullptr, &m_swapChain);
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to create Swap Chain", "Error", MB_OK);
        return false;
    }

    //Create Back Buffer
    D2D1_BITMAP_PROPERTIES1 backBufferDesc{};
    backBufferDesc.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
    backBufferDesc.pixelFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE;
    backBufferDesc.dpiX = static_cast<FLOAT>(dpi);
    backBufferDesc.dpiY = static_cast<FLOAT>(dpi);
    backBufferDesc.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;

    IDXGISurface* dxgiBackBuffer;
    m_swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer));

    hr = m_context->CreateBitmapFromDxgiSurface(dxgiBackBuffer, &backBufferDesc, &m_backBuffer);
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to create back buffer", "Error", MB_OK);
        return false;
    }

    m_context->SetTarget(m_backBuffer);

    //Init DirectWrite

    //Create DWrite Factory
    if (!m_DWriteFactory) {
        hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&m_DWriteFactory));
        if (FAILED(hr)) {
            MessageBox(nullptr, "Failed to create DWrite Factory", "Error", MB_OK);
            return false;
        }
    }

    d3d_device->Release();
    d3d_context->Release();
    dxgiDevice->Release();
    dxgiAdapter->Release();
    dxgiFactory->Release();
    dxgiBackBuffer->Release();
    return true;
}
 
Ultima modifica:
@DispatchCode thanks, ho gia avuto abbastanza problemi cercando di capire come dovevo intrerpretare l'istruzione in base all' endianness (ARM può operare sia in small endian che big endian da quel che ho capito) perchè non mi era chiaro neppure che rappresentazione fosse usata nel grafico nella prima immagine, un pò deludente il fatto che ho finito per usare un disassembler gia funzionante come reference e modificato il modo in cui interpretavo istruzioni fino a che lo stesso input non mi ha dato l'output in quel sito lì, percui ho deciso che valeva la pena spendere un pò di tempo in più ed assicurarmi di capire davvero cosa sta succedendo, visto che riesco a ragionare molto meglio con tutte le info a portata di mano.
Devo ancora aggiungere il display di "CPSR" (registro con tutte le flag dello stato corrente del processore) ed in alto a destra magari in futuro una stack di esecuzione delle istruzioni.

@driverfury Windows, Direct3D, Direct2D, DirectWrite, sotto la classe base dalla quale derivano le mie finestre, tutta boilerplate:
C++:
#include "GBAEmulator_PCH.h"
#include "D2DWindowBase.h"
#include "GBAEmulator.h"

D2DWindowBase::D2DWindowBase(LONG width, LONG height, HINSTANCE hInstance, GBAEmulator* emuInstance)
    : m_isValid{ false }
    , m_winWidth{ width }
    , m_winHeight{ height }
    , m_background(0.047f, 0.062f, 0.129f, 1.f)
    , m_hInstance{ hInstance }
    , m_emu{ emuInstance }
    , m_hwnd{ nullptr }
    , m_device{ nullptr }
    , m_context{ nullptr }
    , m_swapChain{ nullptr }
    , m_backBuffer{ nullptr }
{
}

D2DWindowBase::~D2DWindowBase()
{
    m_isValid = false;
    if (m_DWriteFactory) {
        m_DWriteFactory->Release();
    }
    if (m_device) {
        m_device->Release();
    }
    if (m_context) {
        m_context->Release();
    }
    if (m_swapChain) {
        m_swapChain->Release();
    }
    if (m_backBuffer) {
        m_backBuffer->Release();
    }
}

bool D2DWindowBase::IsValid() const
{
    return m_isValid;
}

LONG D2DWindowBase::Width() const
{
    return m_winWidth;
}

LONG D2DWindowBase::Height() const
{
    return m_winHeight;
}

HWND D2DWindowBase::Hwnd() const
{
    return m_hwnd;
}

//This method shouldn't rely on the m_emu variable because
//not all emu member variables are ensured to be initialized at this point.
bool D2DWindowBase::InitWindow(const std::string& windowCaptionText, const WNDCLASSEX& wndClass, DWORD windowStyle, bool bMenu)
{
    //Register Class
    //The check below prevents trying to register a previously registered class again
    WNDCLASSEX temp{};
    if (!GetClassInfoEx(m_hInstance, wndClass.lpszClassName, &temp)) {

        if (!RegisterClassEx(&wndClass)) {
            MessageBox(nullptr, "Failed to register window class", "Error", MB_OK);
            return false;
        }
    }

    //Create Window

    DWORD windowExStyle = 0;

    RECT client{ 0, 0, m_winWidth, m_winHeight };

    SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
    UINT dpi = GetDpiForSystem();

    AdjustWindowRectExForDpi(&client, windowStyle, bMenu, windowExStyle, dpi);

    m_winWidth = client.right - client.left;
    m_winHeight = client.bottom - client.top;

    int x = GetSystemMetrics(SM_CXSCREEN);
    int y = GetSystemMetrics(SM_CYSCREEN);
    x = x / 2 - m_winWidth / 2;
    y = y / 2 - m_winHeight / 2;

    m_hwnd = CreateWindowEx(windowExStyle,
        wndClass.lpszClassName,
        windowCaptionText.c_str(),
        windowStyle,
        x, y, m_winWidth, m_winHeight,
        nullptr,
        nullptr,
        m_hInstance,
        m_emu);

    if (!m_hwnd) {
        MessageBox(nullptr, "Failed to create window", "Error", MB_OK);
        return false;
    }

    //Init Direct3D

    HRESULT hr;
    ID3D11Device* d3d_device;
    ID3D11DeviceContext* d3d_context;

    UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

    const UINT featureCount = 7;
    D3D_FEATURE_LEVEL featureLevels[featureCount] = {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1
    };

    hr = D3D11CreateDevice(nullptr,
        D3D_DRIVER_TYPE_HARDWARE,
        nullptr,
        creationFlags,
        featureLevels,
        featureCount,
        D3D11_SDK_VERSION,
        &d3d_device,
        nullptr,
        &d3d_context);
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to initialize D3D", "Error", MB_OK);
        return false;
    }

    //Init Direct2D

    IDXGIDevice* dxgiDevice;
    d3d_device->QueryInterface(&dxgiDevice);

    D2D1_CREATION_PROPERTIES D2D1DeviceDesc{};
    D2D1DeviceDesc.threadingMode = D2D1_THREADING_MODE_SINGLE_THREADED;
    D2D1DeviceDesc.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;

    hr = D2D1CreateDevice(dxgiDevice, D2D1DeviceDesc, &m_device);
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to create D2D1 Device", "Error", MB_OK);
        return false;
    }

    hr = m_device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &m_context);
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to create D2D1 Context", "Error", MB_OK);
        return false;
    }

    //Create Swap Chain
    IDXGIAdapter* dxgiAdapter = nullptr;
    hr = dxgiDevice->GetAdapter(&dxgiAdapter);
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to get Adapter", "Error", MB_OK);
        return false;
    }

    IDXGIFactory2* dxgiFactory = nullptr;
    hr = dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to get dxgiFactory", "Error", MB_OK);
        return false;
    }

    //swap chain desc
    DXGI_SWAP_CHAIN_DESC1 swapChainDesc{};
    swapChainDesc.Width = m_winWidth;
    swapChainDesc.Height = m_winHeight;
    swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    swapChainDesc.Scaling = DXGI_SCALING_NONE;
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.SampleDesc.Quality = 0;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
    swapChainDesc.BufferCount = 2;
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;

    dxgiFactory->CreateSwapChainForHwnd(d3d_device, m_hwnd, &swapChainDesc, nullptr, nullptr, &m_swapChain);
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to create Swap Chain", "Error", MB_OK);
        return false;
    }

    //Create Back Buffer
    D2D1_BITMAP_PROPERTIES1 backBufferDesc{};
    backBufferDesc.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
    backBufferDesc.pixelFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE;
    backBufferDesc.dpiX = static_cast<FLOAT>(dpi);
    backBufferDesc.dpiY = static_cast<FLOAT>(dpi);
    backBufferDesc.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;

    IDXGISurface* dxgiBackBuffer;
    m_swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer));

    hr = m_context->CreateBitmapFromDxgiSurface(dxgiBackBuffer, &backBufferDesc, &m_backBuffer);
    if (FAILED(hr)) {
        MessageBox(nullptr, "Failed to create back buffer", "Error", MB_OK);
        return false;
    }

    m_context->SetTarget(m_backBuffer);

    //Init DirectWrite

    //Create DWrite Factory
    if (!m_DWriteFactory) {
        hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&m_DWriteFactory));
        if (FAILED(hr)) {
            MessageBox(nullptr, "Failed to create DWrite Factory", "Error", MB_OK);
            return false;
        }
    }

    d3d_device->Release();
    d3d_context->Release();
    dxgiDevice->Release();
    dxgiAdapter->Release();
    dxgiFactory->Release();
    dxgiBackBuffer->Release();
    return true;
}
Il disassembler che stai facendo è stupendo.

Anche se io l'avrei fatto a linea di comando per non perdere troppo tempo nello scrivere la parte grafica.

Anyway... delle feature che puoi aggiungere sono tipo qualche funzionalità di debugging: ti mostra la memoria, passa all'istruzione successiva ecc...
 
Comunque bellissimo progetto, si vede che sei in gamba.

Se ti serve una mano puoi contattarmi tranquillamente, mi farebbe piacere far parte di questo progetto.
 
Grazie ragazzi :)
@driverfury preferisco procedere da solo perchè è una sfida personale, se dovessi accettare mi sentirei cheater e la cosa mi peserebbe, voglio vedere se ci riesco più o meno da solo (eccetto chiedere qualche suggerimento se proprio mi blocco)
Cmq il mio suggerimento è di provare anche tu (magari partendo da uno piu semplice se non hai altre esperienze al riguardo) così possiamo comparare i risultati :)

@Slaw L'ispirazione è partita da un tutorial su come emulare Chip-8 -> http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/ , anche se ho smesso di leggere a "GameLoop" e sono andato ad implementarlo per i fatti miei, non l'ho perfezionato ma pong funziona xD https://github.com/MarcusAseth/Chip-8-Emulator

In pratica emulare il chip è ti permette di capire i concetti chiave (come registri, stack pointer etc...) e gestire poche istruzioni allo stesso tempo, quindi ottimo primo passo
 
Fatto il refactoring della funzione che disegnava il frame dell'istruzione (l'immagine nella pagina precedente), adesso è così:

C++:
void Disassembler::MakeInstructionFrame(const std::vector<uint8_t>& fields, const std::vector<std::wstring>& labels, EInstructionType type)
{
    FLOAT labelHeight = 25.f;
    FLOAT labelOffset = 8.f;
    FLOAT spacersThickness = 3.f;
    FLOAT sideSpacing = m_instrFrameSideSpacing;
    FLOAT bitWidth = m_instrFrameBitWidth;
    const D2D1_RECT_F& rect = m_instrFrameRect;

    D2DRect valueFrameRect(  rLeft{ rect.left },
                              rTop{ rect.top },
                            rRight{ rect.right },
                           rBottom{ rect.bottom - labelHeight }
    );
    D2DRect labelFrameRect(  rLeft{ rect.left },
                              rTop{ valueFrameRect.bottom + labelOffset },
                            rRight{ rect.right },
                           rBottom{ rect.bottom }
    );

    InstructionFrames[EnumVal(type)] = DrawableBase(m_instrFrameRect, m_fillBrushWhite);
    auto& currFrame = InstructionFrames[EnumVal(type)];

    //Child[0] = values    Child[1] = labels    Rest = RectFrame / Spacers
    DrawableBase* valueFrame = currFrame.Append(make_unique<DrawableBase>(valueFrameRect, m_fillBrushWhite));
    DrawableBase* labelFrame = currFrame.Append(make_unique<DrawableBase>(labelFrameRect, m_fillBrushWhite));
    currFrame.Append(make_unique<DrawableFrame>(valueFrameRect, m_fillBrushWhite, 3.f));

    valueFrameRect.right = sideSpacing + (bitWidth * fields[0] );
    labelFrameRect.right = sideSpacing + (bitWidth * fields[0] );

    for (size_t ID = 0, count = fields.size(); ID < count; ID++)
    {
        FLOAT HOffset = sideSpacing + fields[ID] * bitWidth;
        valueFrameRect.right = HOffset;
        labelFrameRect.right = HOffset;
        //add value
        valueFrame->Append(make_unique<TextEntry>(valueFrameRect, L"", m_textFormat2,
                                                  m_fillBrushWhite, DWRITE_TEXT_ALIGNMENT_CENTER));
        //add label
        labelFrame->Append(make_unique<TextEntry>(labelFrameRect, labels[ID], m_textFormat2,
                                                  m_fillBrushWhite, DWRITE_TEXT_ALIGNMENT_CENTER));
        ////add spacers
        if (ID != count - 1) {
            currFrame.Append(make_unique<Line>(D2D1_POINT_2F{ HOffset, valueFrameRect.top },
                                               D2D1_POINT_2F{ HOffset, valueFrameRect.bottom },
                                               m_fillBrushWhite, spacersThickness));
        }
        valueFrameRect.left = valueFrameRect.right;
        labelFrameRect.left = labelFrameRect.right;
    }
}

Vantaggi: prima l'istruzione mostrata a schermo era una serie sparsa di draw call provenienti da diverse funzioni ognuna che disegnava un tipo di elemento, adesso la preview dell'istruzione è un singolo elemento di tipo "DrawableBase" che contiene una lista di childs (value, labels, linee di separazione, cornice), percui se muovo il drawable base tutti gli altri seguono, e quindi non escludo in futuro che le nuove istruzioni siano animate, ovvero entrano da destra dello schermo mentre la precedente esce verso il basso Una cosa abbastanza inutile, lo so :lol:

Secondo vantaggio, creare un nuovo layout per un nuovo tipo di istruzione richiede soltanto questo codice:
C++:
    vector<uint8_t> fields{ 4 , 7 , 11 , 12 , 16 , 20 , 25 , 27 , 28 , 32 };
    vector<wstring> labels{ { L"cond" }, { L"0 0 0" }, { L"opcode" }, { L"S" }, { L"Rn" }, { L"Rd" },
                            { L"shift amount" }, { L"shift" }, { L"0" }, { L"Rm" }
    };

    MakeInstructionFrame(fields, labels, EInstructionType::DATA_PROCESSING_IMM_SHIFT);

I numeri dentro "field" indicano la distanza delle linee di separazione e la larghezza dei campi dentro i quali i valori sono mostrati, labels indica le label che corrispondono ad ogni campo, quindi adesso aggiungere nuovi layout è davvero molto semplice.

Stronger Types.
Dopo la 20esima volta che ho dovuto fare "Peek Definition" su un oggetto di tipo D2D1_RECT_F (che contiene 4 FLOAT) per vedere in che ordine dovevo passare left,right,top,bottom, ho deciso di usare "strong types".
C++:
template<typename T, typename Parameter>
class NamedType
{
public:
    explicit NamedType() = default;
    explicit NamedType(T v) :value(v) {}

    operator T() const { return value; }
    T value;
};
C++:
il codice sopra permette di fare questo:
using rLeft = NamedType<FLOAT, struct rLeftType>;
using rTop = NamedType<FLOAT, struct rTopType>;
using rRight = NamedType<FLOAT, struct rRightType>;
using rBottom = NamedType<FLOAT, struct rBottomType>;
ovvero rLeft , rTop, rRight, rBottom sono tutte classi diverse(per via del secondo "template argument" che è uno struct unico diverso per tutti) con definito un operatore per la conversione implicita in FLOAT, il vantaggio è che una funzione che vuole "rLeft" non accetta che per sbaglio gli si passi uno degli altri.
Quindi adesso posso fare questo:
C++:
class D2DRect
{
public:
    D2DRect() = default;
    D2DRect(rLeft left, rTop top, rRight right, rBottom bottom)
        : left{ left }, top{ top }, right{ right }, bottom{ bottom } {}

    operator D2D1_RECT_F()const { return D2D1_RECT_F{ left, top, right, bottom }; }
    FLOAT left, top, right, bottom;
};
il constructor di questo D2DRect richiede quelle 4 classi in un ordine ben preciso, se si sbaglia ordine il codice non compila. Di conseguenza il codice diventa molto più chiaro su cosa sta facendo e meno "error prone". Esempio:
C++:
    D2DRect pos( rLeft{ 570.f },
                  rTop{ 510.f },
                rRight{ 800.f },
               rBottom{ 610.f }
    );

Se non conoscevate strong types, adesso non avete scuse per non usarli :P
 
Ultima modifica:
Come si vede nell'immagine sopra, quell'istruzione è un MOV immediate, non dovrebbe avere il campo "Rn" poichè nel manuale è marcato come SBZ (should be Zero), eppure in alto appare Rn perchè la classificazione generica delle istruzioni data nell'immagine del manuale (nella pagina precedente) fà rientrare questa istruzione nella "Data Processing Immediate", percui è chiaro che non tutte le istruzioni calzano alla perfezione in quella categorizzazione.
Non mi piace il fatto il fatto che posso chiamare il getter Rn() per un'istruzione che non dovrebbe averlo, prima o poi chiamerò Rn() al posto di Rd() e finiro a spendere ore cercando il bug.
L'unica soluzione che mi è venuta in mente è inheritance (sicuramente ci sono soluzioni brillanti che richiedono l'uso di template, percui sono ancora al di fuori della mia portata :D ) per cui al momento ho il codice sotto.
In questo modo posso assemblare le classi in base ai campi che devono avere ed ottenere dunque soltanto i getters che hanno senso per una data istruzione, quindi come nell'esempio nel codice sotto, Instr_MOV è una versione meno generica di Instr_DataProcImm (non ha member variable e getter per Rn).

C++:
#define MAKE_INSTR_PIECE(T, CLASS_NAME, INIT_VAL)            \
class P_##CLASS_NAME                                        \
{                                                            \
public: P_##CLASS_NAME() { m_##CLASS_NAME = INIT_VAL; }        \
    T CLASS_NAME()const { return m_##CLASS_NAME; }            \
private:                                                    \
    T m_##CLASS_NAME;                                        \
};                                                            \

MAKE_INSTR_PIECE(ECond  , Cond     , ToCond(g_rawInstruction))
MAKE_INSTR_PIECE(EOpcode, Opcode1  , ToOpcode1(g_rawInstruction))
MAKE_INSTR_PIECE(Byte   , S        ,   GetBit(20, g_rawInstruction))
MAKE_INSTR_PIECE(Byte   , L        ,   GetBit(24, g_rawInstruction))
MAKE_INSTR_PIECE(Byte   , Rn       , GetBits8(19, 16, g_rawInstruction))
MAKE_INSTR_PIECE(Byte   , Rd       , GetBits8(15, 12, g_rawInstruction))
MAKE_INSTR_PIECE(Byte   , Rm       , GetBits8( 3,  0, g_rawInstruction))
MAKE_INSTR_PIECE(Byte   , ShiftImm , GetBits8(11,  7, g_rawInstruction))
MAKE_INSTR_PIECE(Byte   , Shift    , GetBits8( 6,  5, g_rawInstruction))
MAKE_INSTR_PIECE(Byte   , RotateImm, GetBits8(11,  8, g_rawInstruction))
MAKE_INSTR_PIECE(Byte   , Immed8   , GetBits8( 7,  0, g_rawInstruction))
MAKE_INSTR_PIECE(Byte   , Immed24  , GetBits8(23,  0, g_rawInstruction))


//Classes
struct Instr_DataProcImm :          P_Cond, P_Opcode1, P_S, P_Rn, P_Rd, P_RotateImm, P_Immed8 {};
struct Instr_DataProcImmShift :     P_Cond, P_Opcode1, P_S, P_Rn, P_Rd, P_ShiftImm, P_Shift, P_Rm {};
struct Instr_BranchAndBranchWLink : P_Cond, P_L, P_Immed24 {};
struct Instr_MOV :                  P_Cond, P_Opcode1, P_S,       P_Rd, P_RotateImm, P_Immed8 {};

//Variant
using Instruction = std::variant<
    std::monostate,
    Instr_DataProcImm,
    Instr_DataProcImmShift,
    Instr_BranchAndBranchWLink,
    Instr_MOV
>;

Il modo in cui funziona è semplice, ho una funzione chiamata ToInstructionType(rawInstruction) nella quale posso inserire regole più specializzate per, ad esempio, dedurre il tipo EInstructionType::INSTR_MOV anzichè il generico EInstructionType:: DATA_PROCESSING_IMMEDIATE , nel codice sotto DecodeInstruction() conserva il giusto tipo di classe in una std::variant (che può contenere tutte le classi Instr_ mostrate sopra) in base al tipo di istruzione corrente, percui adesso è abbastanza semplice aggiungere altre classi scegliendo i pezzi che mi servono, ed ottenendo accesso soltanto alle member variable che hanno senso per un determinato tipo di codifica.
Cmq spero che tutta questa inheritance non sia una cosa brutta per le performance :D


C++:
bool GBAEmulator::DecodeInstruction()
{
    switch (instructionType) {
    case EInstructionType::DATA_PROCESSING_IMM_SHIFT:
        instruction = Instr_DataProcImmShift();
        break;
    case EInstructionType::DATA_PROCESSING_IMMEDIATE:
        instruction = Instr_DataProcImm();
        break;
    case EInstructionType::BRANCH_AND_BRANCH_W_LINK:
        instruction = Instr_BranchAndBranchWLink();
        break;
    case EInstructionType::INSTR_MOV:
        instruction = Instr_MOV();
        break;
    default:
        return UnhandledInstructionError();
    }
    return true;
}
 
Ultima modifica:
mi spiace deludervi ma purtroppo l'ho abbandonato, ho abbandonato la programmazione in generale, era un hobby interessante ma non ero convinto facesse al caso mio :\
Ma come daiiii, un po' di competenze le hai insomma :(
Cosa dovrei dire io che dopo due anni a malapena so stampare un hello world a schermo? :lol:
 
mi spiace deludervi ma purtroppo l'ho abbandonato, ho abbandonato la programmazione in generale, era un hobby interessante ma non ero convinto facesse al caso mio :\
Peccato, se non ci sono nuovi spunti sono costretto a chiudere.
 
mi spiace deludervi ma purtroppo l'ho abbandonato, ho abbandonato la programmazione in generale, era un hobby interessante ma non ero convinto facesse al caso mio :\
Decisione saggia, un hobby deve essere qualcosa che fa piacere fare, indipendente se si sia bravi o meno, e bisogna dare priorita' ai nostri interessi.
 
Stato
Discussione chiusa ad ulteriori risposte.
Pubblicità
Pubblicità
Indietro
Top