]> git.unchartedbackwaters.co.uk Git - francis/winuae.git/commitdiff
genlock video file and capture device support.
authorToni Wilen <twilen@winuae.net>
Sat, 8 Oct 2016 14:01:41 +0000 (17:01 +0300)
committerToni Wilen <twilen@winuae.net>
Sat, 8 Oct 2016 14:01:41 +0000 (17:01 +0300)
cfgfile.cpp
include/options.h
include/videograb.h [new file with mode: 0644]
od-win32/win32_videograb.cpp [new file with mode: 0644]
specialmonitors.cpp

index 492ae2abf6302d974aa302cf4dee089907f5f7f9..d73dda6fa2f588a95cb99af8177da8a5ee41ca58 100644 (file)
@@ -210,7 +210,7 @@ static const TCHAR *cdmodes[] = { _T("disabled"), _T(""), _T("image"), _T("ioctl
 static const TCHAR *cdconmodes[] = { _T(""), _T("uae"), _T("ide"), _T("scsi"), _T("cdtv"), _T("cd32"), 0 };
 static const TCHAR *specialmonitors[] = { _T("none"), _T("autodetect"), _T("a2024"), _T("graffiti"),
 _T("ham_e"), _T("ham_e_plus"), _T("videodac18"), _T("avideo12"), _T("avideo24"), _T("firecracker24"), _T("dctv"), _T("opalvision"), _T("colorburst"), 0 };
-static const TCHAR *genlockmodes[] = { _T("none"), _T("noise"), _T("testcard"), NULL };
+static const TCHAR *genlockmodes[] = { _T("none"), _T("noise"), _T("testcard"), _T("image"), _T("video"), _T("stream"), NULL };
 static const TCHAR *ppc_implementations[] = {
        _T("auto"),
        _T("dummy"),
@@ -1833,9 +1833,10 @@ void cfgfile_save_options (struct zfile *f, struct uae_prefs *p, int type)
        cfgfile_write_bool (f, _T("immediate_blits"), p->immediate_blits);
        cfgfile_dwrite_str (f, _T("waiting_blits"), waitblits[p->waiting_blits]);
        cfgfile_write_bool (f, _T("ntsc"), p->ntscmode);
-       cfgfile_write_bool (f, _T("genlock"), p->genlock);
+       cfgfile_write_bool(f, _T("genlock"), p->genlock);
        cfgfile_dwrite_str(f, _T("genlockmode"), genlockmodes[p->genlock_image]);
        cfgfile_dwrite_str(f, _T("genlock_image"), p->genlock_image_file);
+       cfgfile_dwrite_str(f, _T("genlock_video"), p->genlock_video_file);
        cfgfile_dwrite(f, _T("genlock_mix"), _T("%d"), p->genlock_mix);
        cfgfile_dwrite_str(f, _T("monitoremu"), specialmonitors[p->monitoremu]);
 
@@ -4763,6 +4764,7 @@ static int cfgfile_parse_hardware (struct uae_prefs *p, const TCHAR *option, TCH
                || cfgfile_path (option, value, _T("rtc_file"), p->rtcfile, sizeof p->rtcfile / sizeof (TCHAR), &p->path_rom)
                || cfgfile_path(option, value, _T("picassoiv_rom_file"), p->picassoivromfile, sizeof p->picassoivromfile / sizeof(TCHAR), &p->path_rom)
                || cfgfile_string(option, value, _T("genlock_image"), p->genlock_image_file, sizeof p->genlock_image_file / sizeof(TCHAR))
+               || cfgfile_string(option, value, _T("genlock_video"), p->genlock_video_file, sizeof p->genlock_video_file / sizeof(TCHAR))
                || cfgfile_string(option, value, _T ("pci_devices"), p->pci_devices, sizeof p->pci_devices / sizeof(TCHAR))
                || cfgfile_string (option, value, _T("ghostscript_parameters"), p->ghostscript_parameters, sizeof p->ghostscript_parameters / sizeof (TCHAR)))
                return 1;
@@ -5256,7 +5258,7 @@ void cfgfile_compatibility_romtype(struct uae_prefs *p)
        addbcromtype(p, ROMTYPE_NE2KPCMCIA, p->ne2000pcmcianame[0] != 0, NULL, 0);
        addbcromtype(p, ROMTYPE_NE2KPCI, p->ne2000pciname[0] != 0, NULL, 0);
 
-       static int restricted_net[] = { ROMTYPE_A2065, ROMTYPE_NE2KPCMCIA, ROMTYPE_NE2KPCI, ROMTYPE_NE2KISA, 0 };
+       static int restricted_net[] = { ROMTYPE_A2065, ROMTYPE_NE2KPCMCIA, ROMTYPE_NE2KPCI, ROMTYPE_NE2KISA, ROMTYPE_ARIADNE2, 0 };
        static int restricted_x86[] = { ROMTYPE_A1060, ROMTYPE_A2088, ROMTYPE_A2088T, ROMTYPE_A2286, ROMTYPE_A2386, 0 };
        static int restricted_pci[] = { ROMTYPE_GREX, ROMTYPE_MEDIATOR, ROMTYPE_PROMETHEUS, 0 };
        romtype_restricted(p, restricted_net);
index b8b6a11c8e93be1fa658f019f53e39952265f6ca..5993141eb552ebfd8deffdd83623d48e509fb8ba 100644 (file)
@@ -510,6 +510,7 @@ struct uae_prefs {
        int genlock_image;
        int genlock_mix;
        TCHAR genlock_image_file[MAX_DPATH];
+       TCHAR genlock_video_file[MAX_DPATH];
        int monitoremu;
        double chipset_refreshrate;
        struct chipset_refresh cr[MAX_CHIPSET_REFRESH + 2];
diff --git a/include/videograb.h b/include/videograb.h
new file mode 100644 (file)
index 0000000..f38e52f
--- /dev/null
@@ -0,0 +1,5 @@
+
+bool initvideograb(const TCHAR *filename);
+void uninitvideograb(void);
+bool getvideograb(long **buffer, int *width, int *height);
+void pausevideograb(bool pause);
\ No newline at end of file
diff --git a/od-win32/win32_videograb.cpp b/od-win32/win32_videograb.cpp
new file mode 100644 (file)
index 0000000..fd66000
--- /dev/null
@@ -0,0 +1,299 @@
+
+/* UAE Win32 Video frame grabber support
+ * Toni Wilen 2016
+ */
+
+#include "sysconfig.h"
+
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "sysdeps.h"
+#include "options.h"
+
+#include <windows.h>
+#include <dshow.h>
+#include <atlcomcli.h>
+
+#include "videograb.h"
+
+#pragma comment(lib,"Strmiids.lib") 
+
+// following have been removed from newer SDKs
+
+static const IID IID_ISampleGrabber = { 0x6B652FFF, 0x11FE, 0x4fce, { 0x92, 0xAD, 0x02, 0x66, 0xB5, 0xD7, 0xC7, 0x8F } };
+static const CLSID CLSID_SampleGrabber = { 0xC1F400A0, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
+static const CLSID CLSID_NullRenderer = { 0xC1F400A4, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
+
+interface ISampleGrabberCB : public IUnknown
+{
+       virtual STDMETHODIMP SampleCB(double SampleTime, IMediaSample *pSample) = 0;
+       virtual STDMETHODIMP BufferCB(double SampleTime, BYTE *pBuffer, long BufferLen) = 0;
+};
+interface ISampleGrabber : public IUnknown
+{
+    virtual HRESULT STDMETHODCALLTYPE SetOneShot(BOOL OneShot) = 0;
+    virtual HRESULT STDMETHODCALLTYPE SetMediaType(const AM_MEDIA_TYPE *pType) = 0;
+    virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType(AM_MEDIA_TYPE *pType) = 0;
+    virtual HRESULT STDMETHODCALLTYPE SetBufferSamples(BOOL BufferThem) = 0;
+    virtual HRESULT STDMETHODCALLTYPE GetCurrentBuffer(long *pBufferSize, long *pBuffer) = 0;
+    virtual HRESULT STDMETHODCALLTYPE GetCurrentSample(IMediaSample **ppSample) = 0;
+    virtual HRESULT STDMETHODCALLTYPE SetCallback(ISampleGrabberCB *pCallback, long WhichMethodToCallback) = 0;
+};
+
+// based on code from: http://forum.devmaster.net/t/generating-textures-from-video-frames/8259
+
+static CComPtr<ICaptureGraphBuilder2> graphBuilder;
+static CComPtr<IFilterGraph2> filterGraph;
+static CComPtr<ISampleGrabber> sampleGrabber;
+static CComPtr<IMediaControl> mediaControl;
+static bool videoInitialized;
+static long *frameBuffer;
+static long bufferSize;
+static int videoWidth, videoHeight;
+
+void uninitvideograb(void)
+{
+       write_log(_T("uninitvideograb\n"));
+
+       videoInitialized = false;
+
+       sampleGrabber.Release();
+       if (mediaControl) {
+               mediaControl->Stop();
+       }
+       mediaControl.Release();
+       filterGraph.Release();
+       graphBuilder.Release();
+
+       delete[] frameBuffer;
+       frameBuffer = NULL;
+}
+
+static void FindPin(IBaseFilter* baseFilter, PIN_DIRECTION direction, int pinNumber, IPin** destPin)
+{
+       CComPtr<IEnumPins> enumPins;
+
+       *destPin = NULL;
+
+       if (SUCCEEDED(baseFilter->EnumPins(&enumPins))) {
+               ULONG numFound;
+               IPin* tmpPin;
+
+               while (SUCCEEDED(enumPins->Next(1, &tmpPin, &numFound))) {
+                       PIN_DIRECTION pinDirection;
+
+                       tmpPin->QueryDirection(&pinDirection);
+                       if (pinDirection == direction) {
+                               if (pinNumber == 0) {
+                                       // Return the pin's interface
+                                       *destPin = tmpPin;
+                                       break;
+                               }
+                               pinNumber--;
+                       }
+                       tmpPin->Release();
+               }
+       }
+}
+
+static bool ConnectPins(IBaseFilter* outputFilter, unsigned int outputNum, IBaseFilter* inputFilter, unsigned int inputNum)
+{
+       CComPtr<IPin> inputPin;
+       CComPtr<IPin> outputPin;
+
+       if (!outputFilter || !inputFilter) {
+               write_log(_T("ConnectPins OUT=%d IN=%d\n"), outputFilter != 0, inputFilter != 0);
+               return false;
+       }
+
+       FindPin(outputFilter, PINDIR_OUTPUT, outputNum, &outputPin);
+       FindPin(inputFilter, PINDIR_INPUT, inputNum, &inputPin);
+
+       if (inputPin && outputPin) {
+               HRESULT hr = filterGraph->Connect(outputPin, inputPin);
+               if (SUCCEEDED(hr))
+                       return true;
+               write_log(_T("ConnectPins Connect %08x\n"), hr);
+       } else {
+               write_log(_T("ConnectPins OUTPIN=%d INPIN=%d\n"), outputPin != 0, inputPin != 0);
+
+       }
+       return false;
+}
+
+bool initvideograb(const TCHAR *filename)
+{
+       HRESULT hr;
+
+       uninitvideograb();
+
+       write_log(_T("initvideograb '%s'\n"), filename ? filename : _T("<null>"));
+
+       graphBuilder.CoCreateInstance(CLSID_CaptureGraphBuilder2);
+       filterGraph.CoCreateInstance(CLSID_FilterGraph);
+       graphBuilder->SetFiltergraph(filterGraph);
+       CComPtr<IBaseFilter> sourceFilter;
+
+       if (filename != NULL && filename[0]) {
+               // This takes the absolute filename path and
+               // Loads the appropriate file reader and splitter
+               // Depending in the file type.
+               hr = filterGraph->AddSourceFilter(filename, L"Video Source", &sourceFilter);
+               if (FAILED(hr)) {
+                       write_log(_T("AddSourceFilter failed %08x\n"), hr);
+                       uninitvideograb();
+                       return false;
+               }
+       } else {
+               // capture device mode
+               IMoniker *pMoniker;
+               CComPtr<ICreateDevEnum> pCreateDevEnum;
+               hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, IID_ICreateDevEnum, (void**)&pCreateDevEnum);
+               if (FAILED(hr)) {
+                       write_log(_T("CLSID_SystemDeviceEnum IID_ICreateDevEnum failed %08x\n"), hr);
+                       uninitvideograb();
+                       return false;
+               }
+               CComPtr<IEnumMoniker> pEmum;
+               hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEmum, 0);
+               if (FAILED(hr)) {
+                       write_log(_T("CreateClassEnumerator CLSID_VideoInputDeviceCategory failed %08x\n"), hr);
+                       uninitvideograb();
+                       return false;
+               }
+               if (hr == S_FALSE) {
+                       write_log(_T("initvideograb CreateDevEnum: didn't find any capture devices.\n"));
+                       uninitvideograb();
+                       return false;
+               }
+               pEmum->Reset();
+               ULONG cFetched = 0;
+               //Take the first capture device found
+               hr = pEmum->Next(1, &pMoniker, &cFetched);
+               if (FAILED(hr)) {
+                       write_log(_T("initvideograb Next: didn't find any capture devices.\n"));
+                       uninitvideograb();
+                       return false;
+               }
+               pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&sourceFilter);
+               pMoniker->Release();
+               filterGraph->AddFilter(sourceFilter, L"Video Capture");
+       }
+
+       // Create the Sample Grabber which we will use
+       // To take each frame for texture generation
+       CComPtr<IBaseFilter> grabberFilter;
+       grabberFilter.CoCreateInstance(CLSID_SampleGrabber);
+       grabberFilter->QueryInterface(IID_ISampleGrabber, reinterpret_cast<void**>(&sampleGrabber));
+
+       filterGraph->AddFilter(grabberFilter, L"Sample Grabber");
+
+       // We have to set the 24-bit RGB desire here
+       // So that the proper conversion filters
+       // Are added automatically.
+       AM_MEDIA_TYPE desiredType;
+       memset(&desiredType, 0, sizeof(desiredType));
+       desiredType.majortype = MEDIATYPE_Video;
+       desiredType.subtype = MEDIASUBTYPE_RGB24;
+       desiredType.formattype = FORMAT_VideoInfo;
+
+       sampleGrabber->SetMediaType(&desiredType);
+       sampleGrabber->SetBufferSamples(TRUE);
+
+       // Use pin connection methods instead of 
+       // ICaptureGraphBuilder::RenderStream because of
+       // the SampleGrabber setting we're using.
+       if (!ConnectPins(sourceFilter, 0, grabberFilter, 0)) {
+               uninitvideograb();
+               return false;
+       }
+
+       // A Null Renderer does not display the video
+       // But it allows the Sample Grabber to run
+       // And it will keep proper playback timing
+       // Unless specified otherwise.
+       CComPtr<IBaseFilter> nullRenderer;
+       nullRenderer.CoCreateInstance(CLSID_NullRenderer);
+
+       filterGraph->AddFilter(nullRenderer, L"Null Renderer");
+
+       if (!ConnectPins(grabberFilter, 0, nullRenderer, 0)) {
+               uninitvideograb();
+               return false;
+       }
+
+       // Just a little trick so that we don't have to know
+       // The video resolution when calling this method.
+       bool mediaConnected = false;
+       AM_MEDIA_TYPE connectedType;
+       if (SUCCEEDED(sampleGrabber->GetConnectedMediaType(&connectedType))) {
+               if (connectedType.formattype == FORMAT_VideoInfo) {
+                       VIDEOINFOHEADER* infoHeader = (VIDEOINFOHEADER*)connectedType.pbFormat;
+                       videoWidth = infoHeader->bmiHeader.biWidth;
+                       videoHeight = infoHeader->bmiHeader.biHeight;
+                       mediaConnected = true;
+               }
+               CoTaskMemFree(connectedType.pbFormat);
+       }
+
+       if (!mediaConnected) {
+               uninitvideograb();
+               return false;
+       }
+
+       hr = filterGraph->QueryInterface(IID_IMediaControl, (void**)&mediaControl);
+       if (FAILED(hr)) {
+               uninitvideograb();
+               return false;
+       }
+       if (SUCCEEDED(mediaControl->Run())) {
+               videoInitialized = true;
+               return true;
+       } else {
+               uninitvideograb();
+               return false;
+       }
+}
+
+void pausevideograb(bool pause)
+{
+       if (!videoInitialized)
+               return;
+       if (pause) {
+               mediaControl->Pause();
+       } else {
+               mediaControl->Run();
+       }
+}
+
+bool getvideograb(long **buffer, int *width, int *height)
+{
+       HRESULT hr;
+
+       if (!videoInitialized)
+               return false;
+
+       // Only need to do this once
+       if (!frameBuffer) {
+               // The Sample Grabber requires an arbitrary buffer
+               // That we only know at runtime.
+               // (width * height * 3) bytes will not work.
+               hr = sampleGrabber->GetCurrentBuffer(&bufferSize, NULL);
+               if (FAILED(hr)) {
+                       write_log(_T("getvideograb get size %08x\n"), hr);
+                       return false;
+               }
+               frameBuffer = new long[bufferSize];
+       }
+
+       hr = sampleGrabber->GetCurrentBuffer(&bufferSize, (long*)frameBuffer);
+       if (SUCCEEDED(hr)) {
+               *buffer = frameBuffer;
+               *width = videoWidth;
+               *height = videoHeight;
+               return true;
+       }
+       write_log(_T("getvideograb get buffer %08x\n"), hr);
+       return false;
+}
index cba2db919c00da01519b31b737c0cb31579564c0..2744d091d0db06969f8a8a84443d60bd328b52da 100755 (executable)
 #include "specialmonitors.h"
 #include "debug.h"
 #include "zfile.h"
+#include "videograb.h"
+
+#define VIDEOGRAB 1
+
 
 static int opal_debug = 1;
 
@@ -2104,10 +2108,13 @@ static bool a2024(struct vidbuffer *src, struct vidbuffer *dst)
        return true;
 }
 
-static uae_u8 *genlock_image;
+static uae_u8 *genlock_image_data;
+static bool genlock_video;
 static int genlock_image_width, genlock_image_height, genlock_image_pitch;
+static TCHAR genlock_video_file[MAX_DPATH], genlock_image_file[MAX_DPATH];
 static uae_u8 noise_buffer[1024];
 static uae_u32 noise_seed, noise_add, noise_index;
+static bool genlock_error;
 
 static uae_u32 quickrand(void)
 {
@@ -2152,7 +2159,7 @@ static void __cdecl readcallback(png_structp png_ptr, png_bytep out, png_size_t
        cb->size -= count;
 }
 
-static void load_genlock_image(void)
+static bool load_genlock_image(void)
 {
        extern unsigned char test_card_png[];
        extern unsigned int test_card_png_len;
@@ -2166,18 +2173,19 @@ static void load_genlock_image(void)
        struct png_cb cb;
        png_bytepp row_pp;
        png_size_t cols;
+       bool ok = false;
 
-       xfree(genlock_image);
-       genlock_image = NULL;
+       xfree(genlock_image_data);
+       genlock_image_data = NULL;
 
-       if (currprefs.genlock_image == 3) {
+       if (currprefs.genlock_image_file[0] && currprefs.genlock_image == 3) {
                int size;
                uae_u8 *bb = zfile_load_file(currprefs.genlock_image_file, &size);
-               if (bb) {
-                       file_size = size;
-                       b = bb;
-                       bfree = bb;
-               }
+               if (!bb)
+                       goto end;
+               file_size = size;
+               b = bb;
+               bfree = bb;
        }
 
        if (!png_check_sig(b, 8))
@@ -2219,10 +2227,10 @@ static void load_genlock_image(void)
 
        row_pp = new png_bytep[height];
        
-       genlock_image = xcalloc(uae_u8, width * height * 4);
+       genlock_image_data = xcalloc(uae_u8, width * height * 4);
        
        for (int i = 0; i < height; i++) {
-               row_pp[i] = (png_bytep) &genlock_image[i * genlock_image_pitch];
+               row_pp[i] = (png_bytep) &genlock_image_data[i * genlock_image_pitch];
        }
 
        png_read_image(png_ptr, row_pp);
@@ -2231,45 +2239,144 @@ static void load_genlock_image(void)
        png_destroy_read_struct(&png_ptr, &info_ptr, 0);
 
        delete[] row_pp;
+
+       ok = true;
 end:
        xfree(bfree);
+
+       return ok;
 }
 
 static bool do_genlock(struct vidbuffer *src, struct vidbuffer *dst, bool doublelines, int oddlines)
 {
        int y, x, vdbl, hdbl;
        int ystart, yend, isntsc;
-       int gl_vdbl_l, gl_vdbl_r;
+       int gl_vdbl_l, gl_vdbl_r, gl_vdbl;
        int gl_hdbl_l, gl_hdbl_r, gl_hdbl;
        int gl_hcenter, gl_vcenter;
        int mix1 = 0, mix2 = 0;
 
+       int genlock_image_pixbytes = 4;
+       int genlock_image_red_index = 0;
+       int genlock_image_green_index = 1;
+       int genlock_image_blue_index = 2;
+       bool genlock_image_upsidedown = false;
+
+       uae_u8 *genlock_image = NULL;
+
        isntsc = (beamcon0 & 0x20) ? 0 : 1;
        if (!(currprefs.chipset_mask & CSMASK_ECS_AGNUS))
                isntsc = currprefs.ntscmode ? 1 : 0;
 
-       if (!genlock_image && currprefs.genlock_image == 2) {
-               load_genlock_image();
+#if VIDEOGRAB
+       if (currprefs.genlock_image == 5) {
+               if ((!genlock_video && !genlock_error) || _tcsicmp(_T(":CAPTURE:"), genlock_video_file)) {
+                       _tcscpy(genlock_video_file, _T(":CAPTURE:"));
+                       genlock_video = initvideograb(NULL);
+                       if (!genlock_video) {
+                               genlock_error = true;
+                       }
+               } else {
+                       genlock_error = true;
+               }
+               int vidw = 0, vidh = 0;
+               long *vidbuf = NULL;
+               if (genlock_video && getvideograb(&vidbuf, &vidw, &vidh)) {
+                       genlock_image = (uae_u8*)vidbuf;
+                       genlock_image_width = vidw;
+                       genlock_image_height = vidh;
+                       genlock_image_pixbytes = 3;
+                       genlock_image_pitch = genlock_image_width * genlock_image_pixbytes;
+                       genlock_image_red_index = 2;
+                       genlock_image_green_index = 1;
+                       genlock_image_blue_index = 0;
+                       genlock_image_upsidedown = true;
+                       genlock_error = false;
+               }
+               else {
+                       genlock_error = true;
+               }
+       } else if (currprefs.genlock_image == 4) {
+               if (currprefs.genlock_video_file[0]) {
+                       if ((!genlock_video && !genlock_error) || _tcsicmp(currprefs.genlock_video_file, genlock_video_file)) {
+                               _tcscpy(genlock_video_file, currprefs.genlock_video_file);
+                               genlock_video = initvideograb(currprefs.genlock_video_file);
+                               if (!genlock_video) {
+                                       genlock_error = true;
+                               }
+                       }
+               } else {
+                       genlock_error = true;
+               }
+               int vidw = 0, vidh = 0;
+               long *vidbuf = NULL;
+               if (genlock_video && getvideograb(&vidbuf, &vidw, &vidh)) {
+                       genlock_image = (uae_u8*)vidbuf;
+                       genlock_image_width = vidw;
+                       genlock_image_height = vidh;
+                       genlock_image_pixbytes = 3;
+                       genlock_image_pitch = genlock_image_width * genlock_image_pixbytes;
+                       genlock_image_red_index = 2;
+                       genlock_image_green_index = 1;
+                       genlock_image_blue_index = 0;
+                       genlock_image_upsidedown = true;
+                       genlock_error = false;
+               } else {
+                       genlock_error = true;
+               }
        }
-       if (genlock_image && currprefs.genlock_image != 2) {
-               xfree(genlock_image);
-               genlock_image = NULL;
+#endif
+       if (currprefs.genlock_image == 1) {
+               genlock_error = false;
+       } else if (currprefs.genlock_image == 2) {
+               genlock_error = false;
+               if (!genlock_image_data) {
+                       load_genlock_image();
+               }
+               genlock_image = genlock_image_data;
+       } else if (currprefs.genlock_image == 3) {
+               if (!currprefs.genlock_image_file[0]) {
+                       genlock_error = true;
+               } else {
+                       if ((!genlock_image_data && !genlock_error) || _tcsicmp(genlock_image_file, currprefs.genlock_image_file)) {
+                               _tcscpy(genlock_image_file, currprefs.genlock_image_file);
+                               genlock_error = load_genlock_image() == 0;
+                       }
+               }
+               genlock_image = genlock_image_data;
+       }
+
+       if (genlock_image_data && currprefs.genlock_image != 2 && currprefs.genlock_image != 3) {
+               xfree(genlock_image_data);
+               genlock_image_data = NULL;
+       }
+#if VIDEOGRAB
+       if (genlock_video && currprefs.genlock_image != 4 && currprefs.genlock_image != 5) {
+               uninitvideograb();
+               genlock_video = false;
+       }
+#endif
+       if (currprefs.genlock_image != 4 && currprefs.genlock_image != 5) {
+               genlock_video_file[0] = 0;
+       }
+       if (currprefs.genlock_image != 3) {
+               genlock_image_file[0] = 0;
        }
 
        if (gfxvidinfo.xchange == 1)
-               hdbl = 0;
+               hdbl = 0; // shres
        else if (gfxvidinfo.xchange == 2)
-               hdbl = 1;
+               hdbl = 1; // hires
        else
-               hdbl = 2;
+               hdbl = 2; // lores
 
        gl_hdbl_l = gl_hdbl_r = 0;
        if (genlock_image_width < 600) {
-               gl_hdbl = 0;
-       } else if (genlock_image_width < 1000) {
+               gl_hdbl = 2;
+       } else if (genlock_image_width < 1300) {
                gl_hdbl = 1;
        } else {
-               gl_hdbl = 2;
+               gl_hdbl = 0;
        }
        if (hdbl >= gl_hdbl) {
                gl_hdbl_l = hdbl - gl_hdbl;
@@ -2278,18 +2385,28 @@ static bool do_genlock(struct vidbuffer *src, struct vidbuffer *dst, bool double
        }
 
        if (gfxvidinfo.ychange == 1)
-               vdbl = 0;
+               vdbl = 0; // double
        else
-               vdbl = 1;
+               vdbl = 1; // single
 
        gl_vdbl_l = gl_vdbl_r = 0;
+       if (genlock_image_height < 400) {
+               gl_vdbl = 1;
+       } else {
+               gl_vdbl = 0;
+       }
+       if (vdbl >= gl_vdbl) {
+               gl_vdbl_l = vdbl - gl_vdbl;
+       } else {
+               gl_vdbl_r = gl_vdbl - vdbl;
+       }
 
-       gl_hcenter = (genlock_image_width - ((src->inwidth << hdbl) >> gl_hdbl)) / 2;
+       gl_hcenter = (((genlock_image_width << gl_hdbl_r) >> gl_hdbl_l) - src->inwidth) / 2;
 
        ystart = isntsc ? VBLANK_ENDLINE_NTSC : VBLANK_ENDLINE_PAL;
        yend = isntsc ? MAXVPOS_NTSC : MAXVPOS_PAL;
 
-       gl_vcenter = (((genlock_image_height << gl_vdbl_l) >> gl_vdbl_r) - (((yend - ystart) * 2))) / 2;
+       gl_vcenter = (((genlock_image_height << gl_vdbl_r) >> gl_vdbl_l) - (((yend - ystart) * 2))) / 2;
 
        init_noise();
 
@@ -2309,7 +2426,9 @@ static bool do_genlock(struct vidbuffer *src, struct vidbuffer *dst, bool double
                uae_u8 *line = src->bufmem + yoff * src->rowbytes;
                uae_u8 *dstline = dst->bufmem + (((y * 2 + oddlines) - dst->yoffset) >> vdbl) * dst->rowbytes;
                uae_u8 *line_genlock = row_map_genlock[yoff];
-               int gy = ((((y * 2 + oddlines) - dst->yoffset) << gl_vdbl_l) >> gl_vdbl_r) + gl_vcenter;
+               int gy = ((((y * 2 + oddlines) - dst->yoffset + gl_vcenter) >> gl_vdbl_r) << gl_vdbl_l);
+               if (genlock_image_upsidedown)
+                       gy = (genlock_image_height - 1) - gy;
                uae_u8 *image_genlock = genlock_image + gy * genlock_image_pitch;
                r = g = b = 0;
                noise_add = (quickrand() & 15) | 1;
@@ -2321,13 +2440,17 @@ static bool do_genlock(struct vidbuffer *src, struct vidbuffer *dst, bool double
                        uae_u8 *d2 = d + dst->rowbytes;
 
                        if (is_transparent(*s_genlock)) {
-                               if (genlock_image) {
-                                       int gx = (((x + gl_hcenter) << gl_hdbl_l) >> gl_hdbl_r);
+                               if (genlock_error) {
+                                       r = 0x00;
+                                       g = 0x00;
+                                       b = 0xdd;
+                               } else if (genlock_image) {
+                                       int gx = (((x + gl_hcenter) >> gl_hdbl_r) << gl_hdbl_l);
                                        if (gx >= 0 && gx < genlock_image_width && gy >= 0 && gy < genlock_image_height) {
-                                               uae_u8 *s_genlock_image = image_genlock + gx * 4;
-                                               r = s_genlock_image[0];
-                                               g = s_genlock_image[1];
-                                               b = s_genlock_image[2];
+                                               uae_u8 *s_genlock_image = image_genlock + gx * genlock_image_pixbytes;
+                                               r = s_genlock_image[genlock_image_red_index];
+                                               g = s_genlock_image[genlock_image_green_index];
+                                               b = s_genlock_image[genlock_image_blue_index];
                                        } else {
                                                r = g = b = 0;
                                        }
@@ -3063,12 +3186,12 @@ static bool opalvision(struct vidbuffer *src, struct vidbuffer *dst, bool double
                                }
                                if (c[18] || c[19] || c[11])
                                                write_log(_T("UNIMPLEMENTED BITS!\n"));
-                               opal->dual_play = c[7];
-                               opal->latched = c[10];
-                               opal->wren = c[4];
-                               opal->colcopro = c[5];
+                               opal->dual_play = c[7] != 0;
+                               opal->latched = c[10] != 0;
+                               opal->wren = c[4] != 0;
+                               opal->colcopro = c[5] != 0;
                                opal->bank_field = c[8];
-                               opal->auto_field = c[9];
+                               opal->auto_field = c[9] != 0;
                                opal->active_banks[0] = c[12];
                                opal->active_banks[1] = c[13];
                                opal->active_banks[2] = c[14];
@@ -3091,8 +3214,8 @@ static bool opalvision(struct vidbuffer *src, struct vidbuffer *dst, bool double
                                }
                                if (c[15])
                                        write_log(_T("UNIMPLEMENTED BITS!\n"));
-                               opal->wren = c[4];
-                               opal->colcopro = c[5];
+                               opal->wren = c[4] != 0;
+                               opal->colcopro = c[5] != 0;
                                opal->bank_field = c[11];
                                opal->auto_field = false;
                                opal->active_banks[0] = c[8];
@@ -3200,6 +3323,7 @@ void specialmonitor_reset(void)
 {
        if (!currprefs.monitoremu)
                return;
+       uninitvideograb();
        specialmonitor_store_fmode(-1, -1, 0);
        fc24_reset();
 }