From 6f60eb5efc70523c8ef99b705f074ac7f981ace8 Mon Sep 17 00:00:00 2001 From: nattthebear Date: Sat, 17 Jun 2017 10:07:02 -0400 Subject: [PATCH] Create an abstract base class for waterbox cores, and adapt Virtual Boyee to use it. Not sure yet how useful this is... --- BizHawk.Common/BizInvoke/BizInvoker.cs | 1088 +++---- .../BizHawk.Emulation.Cores.csproj | 2 + .../Consoles/Nintendo/VB/LibVirtualBoyee.cs | 233 +- .../Consoles/Nintendo/VB/VirtualBoyee.cs | 297 +- BizHawk.Emulation.Cores/CoreInventory.cs | 2 +- .../Waterbox/LibWaterboxCore.cs | 194 ++ .../Waterbox/WaterboxCore.cs | 339 ++ output/dll/vb.wbx | Bin 95246 -> 0 bytes output/dll/vb.wbx.gz | Bin 0 -> 37597 bytes waterbox/emulibc/waterboxcore.h | 39 + waterbox/vb/vb.cpp | 1598 +++++----- waterbox/vb/vb.h | 238 +- waterbox/vb/vip.cpp | 2787 ++++++++--------- waterbox/vb/vip.h | 164 +- 14 files changed, 3619 insertions(+), 3362 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Waterbox/LibWaterboxCore.cs create mode 100644 BizHawk.Emulation.Cores/Waterbox/WaterboxCore.cs delete mode 100644 output/dll/vb.wbx create mode 100644 output/dll/vb.wbx.gz create mode 100644 waterbox/emulibc/waterboxcore.h diff --git a/BizHawk.Common/BizInvoke/BizInvoker.cs b/BizHawk.Common/BizInvoke/BizInvoker.cs index 609714d587..d39f68c2f5 100644 --- a/BizHawk.Common/BizInvoke/BizInvoker.cs +++ b/BizHawk.Common/BizInvoke/BizInvoker.cs @@ -1,544 +1,544 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.InteropServices; - -using BizHawk.Common; - -namespace BizHawk.Common.BizInvoke -{ - public static class BizInvoker - { - /// - /// holds information about a proxy implementation, including type and setup hooks - /// - private class InvokerImpl - { - public Type ImplType; - public List> Hooks; - public Action ConnectMonitor; - - public object Create(IImportResolver dll, IMonitor monitor) - { - var ret = Activator.CreateInstance(ImplType); - foreach (var f in Hooks) - { - f(ret, dll); - } - - ConnectMonitor?.Invoke(ret, monitor); - return ret; - } - } - - /// - /// dictionary of all generated proxy implementations and their base types - /// - private static readonly IDictionary Impls = new Dictionary(); - - /// - /// the assembly that all proxies are placed in - /// - private static readonly AssemblyBuilder ImplAssemblyBuilder; - - /// - /// the module that all proxies are placed in - /// - private static readonly ModuleBuilder ImplModuleBuilder; - - static BizInvoker() - { - var aname = new AssemblyName("BizInvokeProxyAssembly"); - ImplAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.Run); - ImplModuleBuilder = ImplAssemblyBuilder.DefineDynamicModule("BizInvokerModule"); - } - - /// - /// get an implementation proxy for an interop class - /// - /// The class type that represents the DLL - public static T GetInvoker(IImportResolver dll) - where T : class - { - InvokerImpl impl; - lock (Impls) - { - var baseType = typeof(T); - if (!Impls.TryGetValue(baseType, out impl)) - { - impl = CreateProxy(baseType, false); - Impls.Add(baseType, impl); - } - } - - if (impl.ConnectMonitor != null) - { - throw new InvalidOperationException("Class was previously proxied with a monitor!"); - } - - return (T)impl.Create(dll, null); - } - - public static T GetInvoker(IImportResolver dll, IMonitor monitor) - where T : class - { - InvokerImpl impl; - lock (Impls) - { - var baseType = typeof(T); - if (!Impls.TryGetValue(baseType, out impl)) - { - impl = CreateProxy(baseType, true); - Impls.Add(baseType, impl); - } - } - - if (impl.ConnectMonitor == null) - { - throw new InvalidOperationException("Class was previously proxied without a monitor!"); - } - - return (T)impl.Create(dll, monitor); - } - - private static InvokerImpl CreateProxy(Type baseType, bool monitor) - { - if (baseType.IsSealed) - { - throw new InvalidOperationException("Can't proxy a sealed type"); - } - - if (!baseType.IsPublic) - { - // the proxy type will be in a new assembly, so public is required here - throw new InvalidOperationException("Type must be public"); - } - - var baseConstructor = baseType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); - if (baseConstructor == null) - { - throw new InvalidOperationException("Base type must have a zero arg constructor"); - } - - var baseMethods = baseType.GetMethods(BindingFlags.Instance | BindingFlags.Public) - .Select(m => new - { - Info = m, - Attr = m.GetCustomAttributes(true).OfType().FirstOrDefault() - }) - .Where(a => a.Attr != null) - .ToList(); - - if (baseMethods.Count == 0) - { - throw new InvalidOperationException("Couldn't find any [BizImport] methods to proxy"); - } - - { - var uo = baseMethods.FirstOrDefault(a => !a.Info.IsVirtual || a.Info.IsFinal); - if (uo != null) - { - throw new InvalidOperationException("Method " + uo.Info.Name + " cannot be overriden!"); - } - - // there's no technical reason to disallow this, but we wouldn't be doing anything - // with the base implementation, so it's probably a user error - var na = baseMethods.FirstOrDefault(a => !a.Info.IsAbstract); - if (na != null) - { - throw new InvalidOperationException("Method " + na.Info.Name + " is not abstract!"); - } - } - - // hooks that will be run on the created proxy object - var postCreateHooks = new List>(); - - var type = ImplModuleBuilder.DefineType("Bizhawk.BizInvokeProxy" + baseType.Name, TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed, baseType); - - var monitorField = monitor ? type.DefineField("MonitorField", typeof(IMonitor), FieldAttributes.Public) : null; - - foreach (var mi in baseMethods) - { - var entryPointName = mi.Attr.EntryPoint ?? mi.Info.Name; - - var hook = mi.Attr.Compatibility - ? ImplementMethodDelegate(type, mi.Info, mi.Attr.CallingConvention, entryPointName, monitorField) - : ImplementMethodCalli(type, mi.Info, mi.Attr.CallingConvention, entryPointName, monitorField); - - postCreateHooks.Add(hook); - } - - var ret = new InvokerImpl - { - Hooks = postCreateHooks, - ImplType = type.CreateType() - }; - if (monitor) - { - ret.ConnectMonitor = (o, m) => o.GetType().GetField(monitorField.Name).SetValue(o, m); - } - - return ret; - } - - /// - /// create a method implementation that uses GetDelegateForFunctionPointer internally - /// - private static Action ImplementMethodDelegate(TypeBuilder type, MethodInfo baseMethod, CallingConvention nativeCall, string entryPointName, FieldInfo monitorField) - { - // create the delegate type - MethodBuilder delegateInvoke; - var delegateType = BizInvokeUtilities.CreateDelegateType(baseMethod, nativeCall, type, out delegateInvoke); - - var paramInfos = baseMethod.GetParameters(); - var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray(); - var returnType = baseMethod.ReturnType; - - // define a field on the class to hold the delegate - var field = type.DefineField( - "DelegateField" + baseMethod.Name, - delegateType, - FieldAttributes.Public); - - var method = type.DefineMethod( - baseMethod.Name, - MethodAttributes.Virtual | MethodAttributes.Public, - CallingConventions.HasThis, - returnType, - paramTypes); - - var il = method.GetILGenerator(); - - Label exc = new Label(); - if (monitorField != null) // monitor: enter and then begin try - { - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, monitorField); - il.Emit(OpCodes.Callvirt, typeof(IMonitor).GetMethod("Enter")); - exc = il.BeginExceptionBlock(); - } - - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, field); - for (int i = 0; i < paramTypes.Length; i++) - { - il.Emit(OpCodes.Ldarg, (short)(i + 1)); - } - - il.Emit(OpCodes.Callvirt, delegateInvoke); - - if (monitorField != null) // monitor: finally exit - { - LocalBuilder loc = null; - if (returnType != typeof(void)) - { - loc = il.DeclareLocal(returnType); - il.Emit(OpCodes.Stloc, loc); - } - - il.Emit(OpCodes.Leave, exc); - il.BeginFinallyBlock(); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, monitorField); - il.Emit(OpCodes.Callvirt, typeof(IMonitor).GetMethod("Exit")); - il.EndExceptionBlock(); - - if (returnType != typeof(void)) - { - il.Emit(OpCodes.Ldloc, loc); - } - } - - il.Emit(OpCodes.Ret); - - type.DefineMethodOverride(method, baseMethod); - - return (o, dll) => - { - var entryPtr = dll.SafeResolve(entryPointName); - var interopDelegate = Marshal.GetDelegateForFunctionPointer(entryPtr, delegateType.CreateType()); - o.GetType().GetField(field.Name).SetValue(o, interopDelegate); - }; - } - - /// - /// create a method implementation that uses calli internally - /// - private static Action ImplementMethodCalli(TypeBuilder type, MethodInfo baseMethod, CallingConvention nativeCall, string entryPointName, FieldInfo monitorField) - { - var paramInfos = baseMethod.GetParameters(); - var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray(); - var nativeParamTypes = new List(); - var returnType = baseMethod.ReturnType; - if (returnType != typeof(void) && !returnType.IsPrimitive) - { - throw new InvalidOperationException("Only primitive return types are supported"); - } - - // define a field on the type to hold the entry pointer - var field = type.DefineField( - "EntryPtrField" + baseMethod.Name, - typeof(IntPtr), - FieldAttributes.Public); - - var method = type.DefineMethod( - baseMethod.Name, - MethodAttributes.Virtual | MethodAttributes.Public, - CallingConventions.HasThis, - returnType, - paramTypes); - - var il = method.GetILGenerator(); - - Label exc = new Label(); - if (monitorField != null) // monitor: enter and then begin try - { - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, monitorField); - il.Emit(OpCodes.Callvirt, typeof(IMonitor).GetMethod("Enter")); - exc = il.BeginExceptionBlock(); - } - - for (int i = 0; i < paramTypes.Length; i++) - { - // arg 0 is this, so + 1 - nativeParamTypes.Add(EmitParamterLoad(il, i + 1, paramTypes[i])); - } - - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, field); - il.EmitCalli(OpCodes.Calli, - nativeCall, - returnType == typeof(bool) ? typeof(byte) : returnType, // undo winapi style bool garbage - nativeParamTypes.ToArray()); - - if (monitorField != null) // monitor: finally exit - { - LocalBuilder loc = null; - if (returnType != typeof(void)) - { - loc = il.DeclareLocal(returnType); - il.Emit(OpCodes.Stloc, loc); - } - - il.Emit(OpCodes.Leave, exc); - il.BeginFinallyBlock(); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, monitorField); - il.Emit(OpCodes.Callvirt, typeof(IMonitor).GetMethod("Exit")); - il.EndExceptionBlock(); - - if (returnType != typeof(void)) - { - il.Emit(OpCodes.Ldloc, loc); - } - } - - // either there's a primitive on the stack and we're expected to return that primitive, - // or there's nothing on the stack and we're expected to return nothing - il.Emit(OpCodes.Ret); - - type.DefineMethodOverride(method, baseMethod); - - return (o, dll) => - { - var entryPtr = dll.SafeResolve(entryPointName); - o.GetType().GetField(field.Name).SetValue(o, entryPtr); - }; - } - - /// - /// load an IntPtr constant in an IL stream - /// - private static void LoadConstant(ILGenerator il, IntPtr p) - { - if (p == IntPtr.Zero) - { - il.Emit(OpCodes.Ldc_I4_0); - } - else if (IntPtr.Size == 4) - { - il.Emit(OpCodes.Ldc_I4, (int)p); - } - else - { - il.Emit(OpCodes.Ldc_I8, (long)p); - } - - il.Emit(OpCodes.Conv_I); - } - - /// - /// load a UIntPtr constant in an IL stream - /// - private static void LoadConstant(ILGenerator il, UIntPtr p) - { - if (p == UIntPtr.Zero) - { - il.Emit(OpCodes.Ldc_I4_0); - } - else if (UIntPtr.Size == 4) - { - il.Emit(OpCodes.Ldc_I4, (int)p); - } - else - { - il.Emit(OpCodes.Ldc_I8, (long)p); - } - - il.Emit(OpCodes.Conv_U); - } - - /// - /// emit a single parameter load with unmanaged conversions - /// - private static Type EmitParamterLoad(ILGenerator il, int idx, Type type) - { - if (type.IsGenericType) - { - throw new InvalidOperationException("Generic types not supported"); - } - - if (type.IsByRef) - { - var et = type.GetElementType(); - if (!et.IsPrimitive && !et.IsEnum) - { - throw new InvalidOperationException("Only refs of primitive or enum types are supported!"); - } - - var loc = il.DeclareLocal(type, true); - il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Stloc, loc); - il.Emit(OpCodes.Conv_I); - return typeof(IntPtr); - } - - if (type.IsArray) - { - var et = type.GetElementType(); - if (!et.IsPrimitive && !et.IsEnum) - { - throw new InvalidOperationException("Only arrays of primitive or enum types are supported!"); - } - - // these two cases aren't too hard to add - if (type.GetArrayRank() > 1) - { - throw new InvalidOperationException("Multidimensional arrays are not supported!"); - } - - if (type.Name.Contains('*')) - { - throw new InvalidOperationException("Only 0-based 1-dimensional arrays are supported!"); - } - - var loc = il.DeclareLocal(type, true); - var end = il.DefineLabel(); - var isNull = il.DefineLabel(); - - il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Brfalse, isNull); - - il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Stloc, loc); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ldelema, et); - il.Emit(OpCodes.Conv_I); - il.Emit(OpCodes.Br, end); - - il.MarkLabel(isNull); - LoadConstant(il, IntPtr.Zero); - il.MarkLabel(end); - - return typeof(IntPtr); - } - - if (typeof(Delegate).IsAssignableFrom(type)) - { - var mi = typeof(Marshal).GetMethod("GetFunctionPointerForDelegate", new[] { typeof(Delegate) }); - var end = il.DefineLabel(); - var isNull = il.DefineLabel(); - - il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Brfalse, isNull); - - il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Call, mi); - il.Emit(OpCodes.Br, end); - - il.MarkLabel(isNull); - LoadConstant(il, IntPtr.Zero); - il.MarkLabel(end); - return typeof(IntPtr); - } - - if (type.IsClass) - { - // non ref of class can just be passed as pointer - var loc = il.DeclareLocal(type, true); - var end = il.DefineLabel(); - var isNull = il.DefineLabel(); - - il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Brfalse, isNull); - - il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Stloc, loc); - il.Emit(OpCodes.Conv_I); - // skip past the methodtable pointer to the first field - il.Emit(IntPtr.Size == 4 ? OpCodes.Ldc_I4_4 : OpCodes.Ldc_I4_8); - il.Emit(OpCodes.Conv_I); - il.Emit(OpCodes.Add); - il.Emit(OpCodes.Br, end); - - il.MarkLabel(isNull); - LoadConstant(il, IntPtr.Zero); - il.MarkLabel(end); - - return typeof(IntPtr); - } - - if (type.IsPrimitive || type.IsEnum) - { - il.Emit(OpCodes.Ldarg, (short)idx); - return type; - } - - throw new InvalidOperationException("Unrecognized parameter type!"); - } - } - - /// - /// mark an abstract method to be proxied by BizInvoker - /// - [AttributeUsage(AttributeTargets.Method)] - public class BizImportAttribute : Attribute - { - public CallingConvention CallingConvention { get; } - - /// - /// Gets or sets the name of entry point; if not given, the method's name is used - /// - public string EntryPoint { get; set; } - - /// - /// Gets or sets a value indicating whether or not to use a slower interop that supports more argument types - /// - public bool Compatibility { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// unmanaged calling convention - public BizImportAttribute(CallingConvention c) - { - CallingConvention = c; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; + +using BizHawk.Common; + +namespace BizHawk.Common.BizInvoke +{ + public static class BizInvoker + { + /// + /// holds information about a proxy implementation, including type and setup hooks + /// + private class InvokerImpl + { + public Type ImplType; + public List> Hooks; + public Action ConnectMonitor; + + public object Create(IImportResolver dll, IMonitor monitor) + { + var ret = Activator.CreateInstance(ImplType); + foreach (var f in Hooks) + { + f(ret, dll); + } + + ConnectMonitor?.Invoke(ret, monitor); + return ret; + } + } + + /// + /// dictionary of all generated proxy implementations and their base types + /// + private static readonly IDictionary Impls = new Dictionary(); + + /// + /// the assembly that all proxies are placed in + /// + private static readonly AssemblyBuilder ImplAssemblyBuilder; + + /// + /// the module that all proxies are placed in + /// + private static readonly ModuleBuilder ImplModuleBuilder; + + static BizInvoker() + { + var aname = new AssemblyName("BizInvokeProxyAssembly"); + ImplAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.Run); + ImplModuleBuilder = ImplAssemblyBuilder.DefineDynamicModule("BizInvokerModule"); + } + + /// + /// get an implementation proxy for an interop class + /// + /// The class type that represents the DLL + public static T GetInvoker(IImportResolver dll) + where T : class + { + InvokerImpl impl; + lock (Impls) + { + var baseType = typeof(T); + if (!Impls.TryGetValue(baseType, out impl)) + { + impl = CreateProxy(baseType, false); + Impls.Add(baseType, impl); + } + } + + if (impl.ConnectMonitor != null) + { + throw new InvalidOperationException("Class was previously proxied with a monitor!"); + } + + return (T)impl.Create(dll, null); + } + + public static T GetInvoker(IImportResolver dll, IMonitor monitor) + where T : class + { + InvokerImpl impl; + lock (Impls) + { + var baseType = typeof(T); + if (!Impls.TryGetValue(baseType, out impl)) + { + impl = CreateProxy(baseType, true); + Impls.Add(baseType, impl); + } + } + + if (impl.ConnectMonitor == null) + { + throw new InvalidOperationException("Class was previously proxied without a monitor!"); + } + + return (T)impl.Create(dll, monitor); + } + + private static InvokerImpl CreateProxy(Type baseType, bool monitor) + { + if (baseType.IsSealed) + { + throw new InvalidOperationException("Can't proxy a sealed type"); + } + + if (!baseType.IsPublic) + { + // the proxy type will be in a new assembly, so public is required here + throw new InvalidOperationException("Type must be public"); + } + + var baseConstructor = baseType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); + if (baseConstructor == null) + { + throw new InvalidOperationException("Base type must have a zero arg constructor"); + } + + var baseMethods = baseType.GetMethods(BindingFlags.Instance | BindingFlags.Public) + .Select(m => new + { + Info = m, + Attr = m.GetCustomAttributes(true).OfType().FirstOrDefault() + }) + .Where(a => a.Attr != null) + .ToList(); + + if (baseMethods.Count == 0) + { + throw new InvalidOperationException("Couldn't find any [BizImport] methods to proxy"); + } + + { + var uo = baseMethods.FirstOrDefault(a => !a.Info.IsVirtual || a.Info.IsFinal); + if (uo != null) + { + throw new InvalidOperationException("Method " + uo.Info.Name + " cannot be overriden!"); + } + + // there's no technical reason to disallow this, but we wouldn't be doing anything + // with the base implementation, so it's probably a user error + var na = baseMethods.FirstOrDefault(a => !a.Info.IsAbstract); + if (na != null) + { + throw new InvalidOperationException("Method " + na.Info.Name + " is not abstract!"); + } + } + + // hooks that will be run on the created proxy object + var postCreateHooks = new List>(); + + var type = ImplModuleBuilder.DefineType("Bizhawk.BizInvokeProxy" + baseType.Name, TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed, baseType); + + var monitorField = monitor ? type.DefineField("MonitorField", typeof(IMonitor), FieldAttributes.Public) : null; + + foreach (var mi in baseMethods) + { + var entryPointName = mi.Attr.EntryPoint ?? mi.Info.Name; + + var hook = mi.Attr.Compatibility + ? ImplementMethodDelegate(type, mi.Info, mi.Attr.CallingConvention, entryPointName, monitorField) + : ImplementMethodCalli(type, mi.Info, mi.Attr.CallingConvention, entryPointName, monitorField); + + postCreateHooks.Add(hook); + } + + var ret = new InvokerImpl + { + Hooks = postCreateHooks, + ImplType = type.CreateType() + }; + if (monitor) + { + ret.ConnectMonitor = (o, m) => o.GetType().GetField(monitorField.Name).SetValue(o, m); + } + + return ret; + } + + /// + /// create a method implementation that uses GetDelegateForFunctionPointer internally + /// + private static Action ImplementMethodDelegate(TypeBuilder type, MethodInfo baseMethod, CallingConvention nativeCall, string entryPointName, FieldInfo monitorField) + { + // create the delegate type + MethodBuilder delegateInvoke; + var delegateType = BizInvokeUtilities.CreateDelegateType(baseMethod, nativeCall, type, out delegateInvoke); + + var paramInfos = baseMethod.GetParameters(); + var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray(); + var returnType = baseMethod.ReturnType; + + // define a field on the class to hold the delegate + var field = type.DefineField( + "DelegateField" + baseMethod.Name, + delegateType, + FieldAttributes.Public); + + var method = type.DefineMethod( + baseMethod.Name, + MethodAttributes.Virtual | MethodAttributes.Public, + CallingConventions.HasThis, + returnType, + paramTypes); + + var il = method.GetILGenerator(); + + Label exc = new Label(); + if (monitorField != null) // monitor: enter and then begin try + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, monitorField); + il.Emit(OpCodes.Callvirt, typeof(IMonitor).GetMethod("Enter")); + exc = il.BeginExceptionBlock(); + } + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, field); + for (int i = 0; i < paramTypes.Length; i++) + { + il.Emit(OpCodes.Ldarg, (short)(i + 1)); + } + + il.Emit(OpCodes.Callvirt, delegateInvoke); + + if (monitorField != null) // monitor: finally exit + { + LocalBuilder loc = null; + if (returnType != typeof(void)) + { + loc = il.DeclareLocal(returnType); + il.Emit(OpCodes.Stloc, loc); + } + + il.Emit(OpCodes.Leave, exc); + il.BeginFinallyBlock(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, monitorField); + il.Emit(OpCodes.Callvirt, typeof(IMonitor).GetMethod("Exit")); + il.EndExceptionBlock(); + + if (returnType != typeof(void)) + { + il.Emit(OpCodes.Ldloc, loc); + } + } + + il.Emit(OpCodes.Ret); + + type.DefineMethodOverride(method, baseMethod); + + return (o, dll) => + { + var entryPtr = dll.SafeResolve(entryPointName); + var interopDelegate = Marshal.GetDelegateForFunctionPointer(entryPtr, delegateType.CreateType()); + o.GetType().GetField(field.Name).SetValue(o, interopDelegate); + }; + } + + /// + /// create a method implementation that uses calli internally + /// + private static Action ImplementMethodCalli(TypeBuilder type, MethodInfo baseMethod, CallingConvention nativeCall, string entryPointName, FieldInfo monitorField) + { + var paramInfos = baseMethod.GetParameters(); + var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray(); + var nativeParamTypes = new List(); + var returnType = baseMethod.ReturnType; + if (returnType != typeof(void) && !returnType.IsPrimitive) + { + throw new InvalidOperationException("Only primitive return types are supported"); + } + + // define a field on the type to hold the entry pointer + var field = type.DefineField( + "EntryPtrField" + baseMethod.Name, + typeof(IntPtr), + FieldAttributes.Public); + + var method = type.DefineMethod( + baseMethod.Name, + MethodAttributes.Virtual | MethodAttributes.Public, + CallingConventions.HasThis, + returnType, + paramTypes); + + var il = method.GetILGenerator(); + + Label exc = new Label(); + if (monitorField != null) // monitor: enter and then begin try + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, monitorField); + il.Emit(OpCodes.Callvirt, typeof(IMonitor).GetMethod("Enter")); + exc = il.BeginExceptionBlock(); + } + + for (int i = 0; i < paramTypes.Length; i++) + { + // arg 0 is this, so + 1 + nativeParamTypes.Add(EmitParamterLoad(il, i + 1, paramTypes[i])); + } + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, field); + il.EmitCalli(OpCodes.Calli, + nativeCall, + returnType == typeof(bool) ? typeof(byte) : returnType, // undo winapi style bool garbage + nativeParamTypes.ToArray()); + + if (monitorField != null) // monitor: finally exit + { + LocalBuilder loc = null; + if (returnType != typeof(void)) + { + loc = il.DeclareLocal(returnType); + il.Emit(OpCodes.Stloc, loc); + } + + il.Emit(OpCodes.Leave, exc); + il.BeginFinallyBlock(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, monitorField); + il.Emit(OpCodes.Callvirt, typeof(IMonitor).GetMethod("Exit")); + il.EndExceptionBlock(); + + if (returnType != typeof(void)) + { + il.Emit(OpCodes.Ldloc, loc); + } + } + + // either there's a primitive on the stack and we're expected to return that primitive, + // or there's nothing on the stack and we're expected to return nothing + il.Emit(OpCodes.Ret); + + type.DefineMethodOverride(method, baseMethod); + + return (o, dll) => + { + var entryPtr = dll.SafeResolve(entryPointName); + o.GetType().GetField(field.Name).SetValue(o, entryPtr); + }; + } + + /// + /// load an IntPtr constant in an IL stream + /// + private static void LoadConstant(ILGenerator il, IntPtr p) + { + if (p == IntPtr.Zero) + { + il.Emit(OpCodes.Ldc_I4_0); + } + else if (IntPtr.Size == 4) + { + il.Emit(OpCodes.Ldc_I4, (int)p); + } + else + { + il.Emit(OpCodes.Ldc_I8, (long)p); + } + + il.Emit(OpCodes.Conv_I); + } + + /// + /// load a UIntPtr constant in an IL stream + /// + private static void LoadConstant(ILGenerator il, UIntPtr p) + { + if (p == UIntPtr.Zero) + { + il.Emit(OpCodes.Ldc_I4_0); + } + else if (UIntPtr.Size == 4) + { + il.Emit(OpCodes.Ldc_I4, (int)p); + } + else + { + il.Emit(OpCodes.Ldc_I8, (long)p); + } + + il.Emit(OpCodes.Conv_U); + } + + /// + /// emit a single parameter load with unmanaged conversions + /// + private static Type EmitParamterLoad(ILGenerator il, int idx, Type type) + { + if (type.IsGenericType) + { + throw new InvalidOperationException("Generic types not supported"); + } + + if (type.IsByRef) + { + var et = type.GetElementType(); + if (!et.IsPrimitive && !et.IsEnum) + { + throw new InvalidOperationException("Only refs of primitive or enum types are supported!"); + } + + var loc = il.DeclareLocal(type, true); + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Stloc, loc); + il.Emit(OpCodes.Conv_I); + return typeof(IntPtr); + } + + if (type.IsArray) + { + var et = type.GetElementType(); + if (!et.IsValueType) + { + throw new InvalidOperationException("Only arrays of value types are supported!"); + } + + // these two cases aren't too hard to add + if (type.GetArrayRank() > 1) + { + throw new InvalidOperationException("Multidimensional arrays are not supported!"); + } + + if (type.Name.Contains('*')) + { + throw new InvalidOperationException("Only 0-based 1-dimensional arrays are supported!"); + } + + var loc = il.DeclareLocal(type, true); + var end = il.DefineLabel(); + var isNull = il.DefineLabel(); + + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Brfalse, isNull); + + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Stloc, loc); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ldelema, et); + il.Emit(OpCodes.Conv_I); + il.Emit(OpCodes.Br, end); + + il.MarkLabel(isNull); + LoadConstant(il, IntPtr.Zero); + il.MarkLabel(end); + + return typeof(IntPtr); + } + + if (typeof(Delegate).IsAssignableFrom(type)) + { + var mi = typeof(Marshal).GetMethod("GetFunctionPointerForDelegate", new[] { typeof(Delegate) }); + var end = il.DefineLabel(); + var isNull = il.DefineLabel(); + + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Brfalse, isNull); + + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Call, mi); + il.Emit(OpCodes.Br, end); + + il.MarkLabel(isNull); + LoadConstant(il, IntPtr.Zero); + il.MarkLabel(end); + return typeof(IntPtr); + } + + if (type.IsClass) + { + // non ref of class can just be passed as pointer + var loc = il.DeclareLocal(type, true); + var end = il.DefineLabel(); + var isNull = il.DefineLabel(); + + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Brfalse, isNull); + + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Stloc, loc); + il.Emit(OpCodes.Conv_I); + // skip past the methodtable pointer to the first field + il.Emit(IntPtr.Size == 4 ? OpCodes.Ldc_I4_4 : OpCodes.Ldc_I4_8); + il.Emit(OpCodes.Conv_I); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Br, end); + + il.MarkLabel(isNull); + LoadConstant(il, IntPtr.Zero); + il.MarkLabel(end); + + return typeof(IntPtr); + } + + if (type.IsPrimitive || type.IsEnum) + { + il.Emit(OpCodes.Ldarg, (short)idx); + return type; + } + + throw new InvalidOperationException("Unrecognized parameter type!"); + } + } + + /// + /// mark an abstract method to be proxied by BizInvoker + /// + [AttributeUsage(AttributeTargets.Method)] + public class BizImportAttribute : Attribute + { + public CallingConvention CallingConvention { get; } + + /// + /// Gets or sets the name of entry point; if not given, the method's name is used + /// + public string EntryPoint { get; set; } + + /// + /// Gets or sets a value indicating whether or not to use a slower interop that supports more argument types + /// + public bool Compatibility { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// unmanaged calling convention + public BizImportAttribute(CallingConvention c) + { + CallingConvention = c; + } + } +} diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 38b5b350a9..c5cdb63b4c 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -1276,6 +1276,7 @@ + @@ -1292,6 +1293,7 @@ + diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/LibVirtualBoyee.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/LibVirtualBoyee.cs index 7504a8f271..924fdc2816 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/LibVirtualBoyee.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/LibVirtualBoyee.cs @@ -1,148 +1,85 @@ -using BizHawk.Common.BizInvoke; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; - -namespace BizHawk.Emulation.Cores.Consoles.Nintendo.VB -{ - public abstract class LibVirtualBoyee - { - private const CallingConvention CC = CallingConvention.Cdecl; - - [StructLayout(LayoutKind.Sequential)] - public struct Rect - { - public int X; - public int Y; - public int W; - public int H; - } - - [StructLayout(LayoutKind.Explicit)] // TODO: find out why Sequential is sometimes ignored on the native layout - public class EmulateSpec - { - // Pitch(32-bit) must be equal to width and >= the "fb_width" specified in the MDFNGI struct for the emulated system. - // Height must be >= to the "fb_height" specified in the MDFNGI struct for the emulated system. - // The framebuffer pointed to by surface->pixels is written to by the system emulation code. - [FieldOffset(0)] - public IntPtr Pixels; - - // Pointer to sound buffer, set by the driver code, that the emulation code should render sound to. - // Guaranteed to be at least 500ms in length, but emulation code really shouldn't exceed 40ms or so. Additionally, if emulation code - // generates >= 100ms, - // DEPRECATED: Emulation code may set this pointer to a sound buffer internal to the emulation module. - [FieldOffset(8)] - public IntPtr SoundBuf; - - // Number of cycles that this frame consumed, using MDFNGI::MasterClock as a time base. - // Set by emulation code. - [FieldOffset(16)] - public long MasterCycles; - - // Set by the system emulation code every frame, to denote the horizontal and vertical offsets of the image, and the size - // of the image. If the emulated system sets the elements of LineWidths, then the width(w) of this structure - // is ignored while drawing the image. - [FieldOffset(24)] - public Rect DisplayRect; - - // Maximum size of the sound buffer, in frames. Set by the driver code. - [FieldOffset(40)] - public int SoundBufMaxSize; - - // Number of frames currently in internal sound buffer. Set by the system emulation code, to be read by the driver code. - [FieldOffset(44)] - public int SoundBufSize; - - // 0 UDLR SelectStartBA UDLR(right dpad) LtrigRtrig 13 - [FieldOffset(48)] - public Buttons Buttons; - - [FieldOffset(52)] - public bool Lagged; - } - - public enum MemoryArea : int - { - Wram, Sram, Rom - } - - public enum Buttons : int - { - Up = 0x200, - Down = 0x100, - Left = 0x80, - Right = 0x40, - Select = 0x800, - Start = 0x400, - B = 0x2, - A = 0x1, - Up_R = 0x10, - Down_R = 0x200, - Left_R = 0x1000, - Right_R = 0x2000, - L = 0x8, - R = 0x4 - } - - [StructLayout(LayoutKind.Sequential)] - public class NativeSettings - { - public int InstantReadHack; - public int DisableParallax; - public int ThreeDeeMode; - public int SwapViews; - public int AnaglyphPreset; - public int AnaglyphCustomLeftColor; - public int AnaglyphCustomRightColor; - public int NonAnaglyphColor; - public int LedOnScale; - public int InterlacePrescale; - public int SideBySideSeparation; - - private static int ConvertColor(Color c) - { - return c.ToArgb(); - } - - public static NativeSettings FromFrontendSettings(VirtualBoyee.Settings s, VirtualBoyee.SyncSettings ss) - { - return new NativeSettings - { - InstantReadHack = ss.InstantReadHack ? 1 : 0, - DisableParallax = ss.DisableParallax ? 1 : 0, - ThreeDeeMode = (int)s.ThreeDeeMode, - SwapViews = s.SwapViews ? 1 : 0, - AnaglyphPreset = (int)s.AnaglyphPreset, - AnaglyphCustomLeftColor = ConvertColor(s.AnaglyphCustomLeftColor), - AnaglyphCustomRightColor = ConvertColor(s.AnaglyphCustomRightColor), - NonAnaglyphColor = ConvertColor(s.NonAnaglyphColor), - LedOnScale = s.LedOnScale, - InterlacePrescale = s.InterlacePrescale, - SideBySideSeparation = s.SideBySideSeparation - }; - } - } - - [UnmanagedFunctionPointer(CC)] - public delegate void InputCallback(); - - [BizImport(CC)] - public abstract bool Load(byte[] rom, int length, [In]NativeSettings settings); - - [BizImport(CC)] - public abstract void GetMemoryArea(MemoryArea which, ref IntPtr ptr, ref int size); - - [BizImport(CC)] - public abstract void Emulate(EmulateSpec espec); - - [BizImport(CC)] - public abstract void HardReset(); - - [BizImport(CC)] - public abstract void SetInputCallback(InputCallback callback); - } -} +using BizHawk.Common.BizInvoke; +using BizHawk.Emulation.Cores.Waterbox; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.VB +{ + public abstract class LibVirtualBoyee : LibWaterboxCore + { + public enum Buttons : int + { + Up = 0x200, + Down = 0x100, + Left = 0x80, + Right = 0x40, + Select = 0x800, + Start = 0x400, + B = 0x2, + A = 0x1, + Up_R = 0x10, + Down_R = 0x200, + Left_R = 0x1000, + Right_R = 0x2000, + L = 0x8, + R = 0x4 + } + + [StructLayout(LayoutKind.Sequential)] + public new class FrameInfo : LibWaterboxCore.FrameInfo + { + public Buttons Buttons; + } + + [StructLayout(LayoutKind.Sequential)] + public class NativeSettings + { + public int InstantReadHack; + public int DisableParallax; + public int ThreeDeeMode; + public int SwapViews; + public int AnaglyphPreset; + public int AnaglyphCustomLeftColor; + public int AnaglyphCustomRightColor; + public int NonAnaglyphColor; + public int LedOnScale; + public int InterlacePrescale; + public int SideBySideSeparation; + + private static int ConvertColor(Color c) + { + return c.ToArgb(); + } + + public static NativeSettings FromFrontendSettings(VirtualBoyee.Settings s, VirtualBoyee.SyncSettings ss) + { + return new NativeSettings + { + InstantReadHack = ss.InstantReadHack ? 1 : 0, + DisableParallax = ss.DisableParallax ? 1 : 0, + ThreeDeeMode = (int)s.ThreeDeeMode, + SwapViews = s.SwapViews ? 1 : 0, + AnaglyphPreset = (int)s.AnaglyphPreset, + AnaglyphCustomLeftColor = ConvertColor(s.AnaglyphCustomLeftColor), + AnaglyphCustomRightColor = ConvertColor(s.AnaglyphCustomRightColor), + NonAnaglyphColor = ConvertColor(s.NonAnaglyphColor), + LedOnScale = s.LedOnScale, + InterlacePrescale = s.InterlacePrescale, + SideBySideSeparation = s.SideBySideSeparation + }; + } + } + + + [BizImport(CC)] + public abstract bool Load(byte[] rom, int length, [In]NativeSettings settings); + + [BizImport(CC)] + public abstract void HardReset(); + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/VirtualBoyee.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/VirtualBoyee.cs index e3468e6e3f..f77fea1ecd 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/VirtualBoyee.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/VirtualBoyee.cs @@ -18,26 +18,31 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.VB { [CoreAttributes("Virtual Boyee", "Ryphecha", true, true, "0.9.44.1", "https://mednafen.github.io/releases/", false)] - public class VirtualBoyee : IEmulator, IVideoProvider, ISoundProvider, IStatable, - IInputPollable, ISaveRam, ISettable + public class VirtualBoyee : WaterboxCore, ISettable { - private PeRunner _exe; private LibVirtualBoyee _boyee; [CoreConstructor("VB")] public VirtualBoyee(CoreComm comm, byte[] rom, Settings settings, SyncSettings syncSettings) + :base(comm, new Configuration + { + DefaultFpsNumerator = 20000000, + DefaultFpsDenominator = 397824, + DefaultWidth = 384, + DefaultHeight = 224, + MaxWidth = 1024, + MaxHeight = 1024, + MaxSamples = 8192, + SystemId = "VB" + }) { - ServiceProvider = new BasicServiceProvider(this); - CoreComm = comm; - _settings = settings ?? new Settings(); _syncSettings = syncSettings ?? new SyncSettings(); // TODO: the way settings work in this core, changing the non-sync ones will invalidate savestates var nativeSettings = LibVirtualBoyee.NativeSettings.FromFrontendSettings(_settings, _syncSettings); - _exe = new PeRunner(new PeRunnerOptions + _boyee = PreInit(new PeRunnerOptions { - Path = comm.CoreFileProvider.DllPath(), Filename = "vb.wbx", SbrkHeapSizeKB = 256, SealedHeapSizeKB = 4 * 1024, @@ -45,76 +50,20 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.VB PlainHeapSizeKB = 256 }); - _boyee = BizInvoker.GetInvoker(_exe, _exe); - if (!_boyee.Load(rom, rom.Length, nativeSettings)) - { - throw new InvalidOperationException("Core rejected the rom"); - } - - _exe.Seal(); - - _inputCallback = InputCallbacks.Call; - InitMemoryDomains(); - InitSaveram(); + throw new InvalidOperationException("Core rejected the rom"); + + PostInit(); } - private bool _disposed = false; - - public void Dispose() - { - if (!_disposed) - { - _exe.Dispose(); - _exe = null; - _disposed = true; - } - } - - public IEmulatorServiceProvider ServiceProvider { get; private set; } - - public unsafe void FrameAdvance(IController controller, bool render, bool rendersound = true) - { - _boyee.SetInputCallback(InputCallbacks.Count > 0 ? _inputCallback : null); - + protected override LibWaterboxCore.FrameInfo FrameAdvancePrep(IController controller) + { if (controller.IsPressed("Power")) - _boyee.HardReset(); - - fixed (int* vp = _videoBuffer) - fixed (short* sp = _soundBuffer) - { - var spec = new LibVirtualBoyee.EmulateSpec - { - Pixels = (IntPtr)vp, - SoundBuf = (IntPtr)sp, - SoundBufMaxSize = _soundBuffer.Length / 2, - Buttons = GetButtons(controller) - }; - - _boyee.Emulate(spec); - BufferWidth = spec.DisplayRect.W; - BufferHeight = spec.DisplayRect.H; - _numSamples = spec.SoundBufSize; - - Frame++; - - IsLagFrame = spec.Lagged; - if (IsLagFrame) - LagCount++; - } + _boyee.HardReset(); + + return new LibVirtualBoyee.FrameInfo { Buttons = GetButtons(controller) }; } - public int Frame { get; private set; } - - public void ResetCounters() - { - Frame = 0; - } - - public string SystemId { get { return "VB"; } } - public bool DeterministicEmulation { get { return true; } } - public CoreComm CoreComm { get; private set; } - #region Controller private LibVirtualBoyee.Buttons GetButtons(IController c) @@ -165,157 +114,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.VB .ToList() }; - public ControllerDefinition ControllerDefinition => VirtualBoyController; - - #endregion - - #region IVideoProvider - - private int[] _videoBuffer = new int[1024 * 1024]; - - public int[] GetVideoBuffer() - { - return _videoBuffer; - } - - public int VirtualWidth => BufferWidth; - public int VirtualHeight => BufferWidth; - - public int BufferWidth { get; private set; } = 384; - public int BufferHeight { get; private set; } = 224; - - public int VsyncNumerator { get; private set; } = 20000000; - - public int VsyncDenominator { get; private set; } = 397824; - - public int BackgroundColor => unchecked((int)0xff000000); - - #endregion - - #region ISoundProvider - - private short[] _soundBuffer = new short[16384]; - private int _numSamples; - - public void SetSyncMode(SyncSoundMode mode) - { - if (mode == SyncSoundMode.Async) - { - throw new NotSupportedException("Async mode is not supported."); - } - } - - public void GetSamplesSync(out short[] samples, out int nsamp) - { - samples = _soundBuffer; - nsamp = _numSamples; - } - - public void GetSamplesAsync(short[] samples) - { - throw new InvalidOperationException("Async mode is not supported."); - } - - public void DiscardSamples() - { - } - - public bool CanProvideAsync => false; - - public SyncSoundMode SyncMode => SyncSoundMode.Sync; - - #endregion - - public int LagCount { get; set; } - public bool IsLagFrame { get; set; } - - private LibVirtualBoyee.InputCallback _inputCallback; - - public IInputCallbackSystem InputCallbacks { get; } = new InputCallbackSystem(); - - #region IStatable - - public bool BinarySaveStatesPreferred - { - get { return true; } - } - - public void SaveStateText(TextWriter writer) - { - var temp = SaveStateBinary(); - temp.SaveAsHexFast(writer); - // write extra copy of stuff we don't use - writer.WriteLine("Frame {0}", Frame); - } - - public void LoadStateText(TextReader reader) - { - string hex = reader.ReadLine(); - byte[] state = new byte[hex.Length / 2]; - state.ReadFromHexFast(hex); - LoadStateBinary(new BinaryReader(new MemoryStream(state))); - } - - public void LoadStateBinary(BinaryReader reader) - { - _exe.LoadStateBinary(reader); - // other variables - Frame = reader.ReadInt32(); - LagCount = reader.ReadInt32(); - IsLagFrame = reader.ReadBoolean(); - // any managed pointers that we sent to the core need to be resent now! - _boyee.SetInputCallback(null); - } - - public void SaveStateBinary(BinaryWriter writer) - { - _exe.SaveStateBinary(writer); - // other variables - writer.Write(Frame); - writer.Write(LagCount); - writer.Write(IsLagFrame); - } - - public byte[] SaveStateBinary() - { - var ms = new MemoryStream(); - var bw = new BinaryWriter(ms); - SaveStateBinary(bw); - bw.Flush(); - ms.Close(); - return ms.ToArray(); - } - - #endregion - - #region Memory Domains - - private unsafe void InitMemoryDomains() - { - var domains = new List(); - - var domainInfo = new[] - { - new { name = "WRAM", area = LibVirtualBoyee.MemoryArea.Wram, writable = true }, - new { name = "CARTRAM", area = LibVirtualBoyee.MemoryArea.Sram, writable = true }, - new { name = "ROM", area = LibVirtualBoyee.MemoryArea.Rom, writable = false } - }; - - foreach (var a in domainInfo) - { - IntPtr ptr = IntPtr.Zero; - int size = 0; - - _boyee.GetMemoryArea(a.area, ref ptr, ref size); - - if (ptr != IntPtr.Zero && size > 0) - { - domains.Add(new MemoryDomainIntPtrMonitor(a.name, MemoryDomain.Endian.Little, - ptr, size, a.writable, 4, _exe)); - } - } - (ServiceProvider as BasicServiceProvider).Register(new MemoryDomainList(domains)); - } + public override ControllerDefinition ControllerDefinition => VirtualBoyController; #endregion @@ -450,57 +249,5 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.VB } #endregion - - #region ISaveRam - - private const int SaveramSize = 65536; - private IntPtr _saveRamPtr; - - private void InitSaveram() - { - int unused = 0; - _boyee.GetMemoryArea(LibVirtualBoyee.MemoryArea.Sram, ref _saveRamPtr, ref unused); - } - - public unsafe bool SaveRamModified - { - get - { - using (_exe.EnterExit()) - { - int* p = (int*)_saveRamPtr; - int* pend = p + SaveramSize / sizeof(int); - - while (p < pend) - { - if (*p++ != 0) - return true; - } - } - return false; - } - } - - public byte[] CloneSaveRam() - { - using (_exe.EnterExit()) - { - var ret = new byte[SaveramSize]; - Marshal.Copy(_saveRamPtr, ret, 0, SaveramSize); - return ret; - } - } - - public void StoreSaveRam(byte[] data) - { - using (_exe.EnterExit()) - { - if (data.Length != SaveramSize) - throw new InvalidOperationException("Saveram size mismatch"); - Marshal.Copy(data, 0, _saveRamPtr, SaveramSize); - } - } - - #endregion ISaveRam } } diff --git a/BizHawk.Emulation.Cores/CoreInventory.cs b/BizHawk.Emulation.Cores/CoreInventory.cs index 153900a623..abf7b55dcc 100644 --- a/BizHawk.Emulation.Cores/CoreInventory.cs +++ b/BizHawk.Emulation.Cores/CoreInventory.cs @@ -174,7 +174,7 @@ namespace BizHawk.Emulation.Cores { foreach (var typ in assy.GetTypes()) { - if (typ.GetInterfaces().Contains(typeof(IEmulator))) + if (!typ.IsAbstract && typ.GetInterfaces().Contains(typeof(IEmulator))) { var coreattr = typ.GetCustomAttributes(typeof(CoreAttributes), false); if (coreattr.Length != 1) diff --git a/BizHawk.Emulation.Cores/Waterbox/LibWaterboxCore.cs b/BizHawk.Emulation.Cores/Waterbox/LibWaterboxCore.cs new file mode 100644 index 0000000000..3110e108ec --- /dev/null +++ b/BizHawk.Emulation.Cores/Waterbox/LibWaterboxCore.cs @@ -0,0 +1,194 @@ +using BizHawk.Common; +using BizHawk.Common.BizInvoke; +using BizHawk.Emulation.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Waterbox +{ + public abstract class LibWaterboxCore + { + public const CallingConvention CC = CallingConvention.Cdecl; + + [StructLayout(LayoutKind.Sequential)] + public class FrameInfo + { + /// + /// pointer to the video buffer; set by frontend, filled by backend + /// + public IntPtr VideoBuffer; + /// + /// pointer to the sound buffer; set by frontend, filled by backend + /// + public IntPtr SoundBuffer; + /// + /// total number of cycles emulated this frame; set by backend + /// + public long Cycles; + /// + /// width of the output image; set by backend + /// + public int Width; + /// + /// height of the output image; set by backend + /// + public int Height; + /// + /// total number of sample pairs produced; set by backend + /// + public int Samples; + /// + /// true if controllers were not read; set by backend + /// + public int Lagged; + } + + [Flags] + public enum MemoryDomainFlags: int + { + None = 0, + /// + /// if false, the domain MUST NOT be written to. + /// in some cases, a segmentation violation might occur + /// + Writable = 1, + /// + /// if true, this memory domain should be used in saveram. + /// can be ignored if the core provides its own saveram implementation + /// + Saverammable = 2, + /// + /// if true, domain is filled with ones (FF) by default, instead of zeros. + /// used in calculating SaveRamModified + /// + OneFilled = 4, + /// + /// desginates the default memory domain + /// + Primary = 8, + /// + /// if true, the most significant bytes are first in multibyte words + /// + YugeEndian = 16, + /// + /// native wordsize. only a hint + /// + WordSize1 = 32, + /// + /// native wordsize. only a hint + /// + WordSize2 = 64, + /// + /// native wordsize. only a hint + /// + WordSize4 = 128, + /// + /// native wordsize. only a hint + /// + WordSize8 = 256 + } + + [StructLayout(LayoutKind.Sequential)] + public struct MemoryArea + { + /// + /// pointer to the data in memory + /// + public IntPtr Data; + /// + /// null terminated strnig naming the memory domain + /// + public IntPtr Name; + /// + /// size of the domain + /// + public long Size; + /// + /// + /// + public MemoryDomainFlags Flags; + } + + [UnmanagedFunctionPointer(CC)] + public delegate void EmptyCallback(); + + public unsafe class WaterboxMemoryDomain : MemoryDomain + { + private readonly IntPtr _data; + private readonly IMonitor _monitor; + private readonly long _addressMangler; + + public override byte PeekByte(long addr) + { + if ((ulong)addr < (ulong)Size) + { + using (_monitor.EnterExit()) + { + return ((byte*)_data)[addr ^ _addressMangler]; + } + } + + throw new ArgumentOutOfRangeException(nameof(addr)); + } + + public override void PokeByte(long addr, byte val) + { + if (Writable) + { + if ((ulong)addr < (ulong)Size) + { + using (_monitor.EnterExit()) + { + ((byte*)_data)[addr ^ _addressMangler] = val; + } + } + else + { + throw new ArgumentOutOfRangeException(nameof(addr)); + } + } + } + + public WaterboxMemoryDomain(MemoryArea m, IMonitor monitor) + { + Name = Marshal.PtrToStringAnsi(m.Name); + EndianType = (m.Flags & MemoryDomainFlags.YugeEndian) != 0 ? Endian.Big : Endian.Little; + _data = m.Data; + Size = m.Size; + Writable = (m.Flags & MemoryDomainFlags.Writable) != 0; + if ((m.Flags & MemoryDomainFlags.WordSize1) != 0) + WordSize = 1; + else if((m.Flags & MemoryDomainFlags.WordSize2) != 0) + WordSize = 2; + else if((m.Flags & MemoryDomainFlags.WordSize4) != 0) + WordSize = 4; + else if((m.Flags & MemoryDomainFlags.WordSize8) != 0) + WordSize = 8; + else + throw new InvalidOperationException("Unknown word size for memory domain"); + _monitor = monitor; + if (EndianType == Endian.Big) + { + _addressMangler = WordSize - 1; + } + else + { + _addressMangler = 0; + } + } + } + + [BizImport(CC)] + public abstract void FrameAdvance([In, Out] FrameInfo frame); + + [BizImport(CC)] + public abstract void GetMemoryAreas([In, Out] MemoryArea[] areas); + + [BizImport(CC)] + public abstract void SetInputCallback(EmptyCallback callback); + } +} diff --git a/BizHawk.Emulation.Cores/Waterbox/WaterboxCore.cs b/BizHawk.Emulation.Cores/Waterbox/WaterboxCore.cs new file mode 100644 index 0000000000..a10bf76316 --- /dev/null +++ b/BizHawk.Emulation.Cores/Waterbox/WaterboxCore.cs @@ -0,0 +1,339 @@ +using BizHawk.Common; +using BizHawk.Common.BizInvoke; +using BizHawk.Common.BufferExtensions; +using BizHawk.Emulation.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Waterbox +{ + public abstract class WaterboxCore : IEmulator, IVideoProvider, ISoundProvider, IStatable, + IInputPollable, ISaveRam + { + protected LibWaterboxCore _core; + protected PeRunner _exe; + protected LibWaterboxCore.MemoryArea[] _memoryAreas; + private LibWaterboxCore.EmptyCallback _inputCallback; + + public class Configuration + { + public int MaxWidth; + public int MaxHeight; + public int DefaultWidth; + public int DefaultHeight; + public int DefaultFpsNumerator; + public int DefaultFpsDenominator; + public int MaxSamples; + public string SystemId; + } + + protected WaterboxCore(CoreComm comm, Configuration c) + { + BufferWidth = c.DefaultWidth; + BufferHeight = c.DefaultHeight; + _videoBuffer = new int[c.MaxWidth * c.MaxHeight]; + _soundBuffer = new short[c.MaxSamples * 2]; + VsyncNumerator = c.DefaultFpsNumerator; + VsyncDenominator = c.DefaultFpsDenominator; + _serviceProvider = new BasicServiceProvider(this); + SystemId = c.SystemId; + CoreComm = comm; + _inputCallback = InputCallbacks.Call; + } + + protected T PreInit(PeRunnerOptions options) + where T : LibWaterboxCore + { + if (options.Path == null) + options.Path = CoreComm.CoreFileProvider.DllPath(); + _exe = new PeRunner(options); + using (_exe.EnterExit()) + { + var ret = BizInvoker.GetInvoker(_exe, _exe); + _core = ret; + return ret; + } + } + + protected void PostInit() + { + using (_exe.EnterExit()) + { + var areas = new LibWaterboxCore.MemoryArea[256]; + _core.GetMemoryAreas(areas); + _memoryAreas = areas.Where(a => a.Data != IntPtr.Zero && a.Size != 0) + .ToArray(); + _saveramAreas = _memoryAreas.Where(a => (a.Flags & LibWaterboxCore.MemoryDomainFlags.Saverammable) != 0) + .ToArray(); + _saveramSize = (int)_saveramAreas.Sum(a => a.Size); + + var memoryDomains = _memoryAreas.Select(a => new LibWaterboxCore.WaterboxMemoryDomain(a, _exe)); + var primaryIndex = _memoryAreas + .Select((a, i) => new { a, i }) + .Single(a => (a.a.Flags & LibWaterboxCore.MemoryDomainFlags.Primary) != 0).i; + var mdl = new MemoryDomainList(memoryDomains.Cast().ToList()); + mdl.MainMemory = mdl[primaryIndex]; + _serviceProvider.Register(mdl); + _exe.Seal(); + } + } + + #region ISaveRam + + private LibWaterboxCore.MemoryArea[] _saveramAreas; + private int _saveramSize; + + public unsafe bool SaveRamModified + { + get + { + if (_saveramSize == 0) + return false; + using (_exe.EnterExit()) + { + foreach (var area in _saveramAreas) + { + int* p = (int*)area.Data; + int* pend = p + area.Size / sizeof(int); + int cmp = (area.Flags & LibWaterboxCore.MemoryDomainFlags.OneFilled) != 0 ? -1 : 0; + + while (p < pend) + { + if (*p++ != cmp) + return true; + } + } + } + return false; + } + } + + public byte[] CloneSaveRam() + { + if (_saveramSize == 0) + return null; + using (_exe.EnterExit()) + { + var ret = new byte[_saveramSize]; + var offs = 0; + foreach (var area in _saveramAreas) + { + Marshal.Copy(area.Data, ret, offs, (int)area.Size); + offs += (int)area.Size; + } + return ret; + } + } + + public void StoreSaveRam(byte[] data) + { + using (_exe.EnterExit()) + { + if (data.Length != _saveramSize) + throw new InvalidOperationException("Saveram size mismatch"); + using (_exe.EnterExit()) + { + var offs = 0; + foreach (var area in _saveramAreas) + { + Marshal.Copy(data, offs, area.Data, (int)area.Size); + offs += (int)area.Size; + } + } + } + } + + #endregion ISaveRam + + #region IEmulator + + protected abstract LibWaterboxCore.FrameInfo FrameAdvancePrep(IController controller); + + public unsafe void FrameAdvance(IController controller, bool render, bool rendersound = true) + { + using (_exe.EnterExit()) + { + _core.SetInputCallback(InputCallbacks.Count > 0 ? _inputCallback : null); + + fixed (int* vp = _videoBuffer) + fixed (short* sp = _soundBuffer) + { + var frame = FrameAdvancePrep(controller); + frame.VideoBuffer = (IntPtr)vp; + frame.SoundBuffer = (IntPtr)sp; + + _core.FrameAdvance(frame); + + Frame++; + if (IsLagFrame = frame.Lagged != 0) + LagCount++; + + BufferWidth = frame.Width; + BufferHeight = frame.Height; + _numSamples = frame.Samples; + } + } + } + + private bool _disposed = false; + + public virtual void Dispose() + { + if (!_disposed) + { + _exe.Dispose(); + _disposed = true; + } + } + + public CoreComm CoreComm { get; } + public int Frame { get; private set; } + public int LagCount { get; set; } + public bool IsLagFrame { get; set; } + + public void ResetCounters() + { + Frame = 0; + } + + protected readonly BasicServiceProvider _serviceProvider; + public IEmulatorServiceProvider ServiceProvider => _serviceProvider; + public string SystemId { get; } + public bool DeterministicEmulation => true; + public IInputCallbackSystem InputCallbacks { get; } = new InputCallbackSystem(); + public abstract ControllerDefinition ControllerDefinition { get; } + + #endregion + + #region IStatable + + public bool BinarySaveStatesPreferred => true; + + public void SaveStateText(TextWriter writer) + { + var temp = SaveStateBinary(); + temp.SaveAsHexFast(writer); + } + + public void LoadStateText(TextReader reader) + { + string hex = reader.ReadLine(); + byte[] state = new byte[hex.Length / 2]; + state.ReadFromHexFast(hex); + LoadStateBinary(new BinaryReader(new MemoryStream(state))); + } + + public void LoadStateBinary(BinaryReader reader) + { + _exe.LoadStateBinary(reader); + // other variables + Frame = reader.ReadInt32(); + LagCount = reader.ReadInt32(); + IsLagFrame = reader.ReadBoolean(); + BufferWidth = reader.ReadInt32(); + BufferHeight = reader.ReadInt32(); + // reset pointers here! + _core.SetInputCallback(null); + } + + public void SaveStateBinary(BinaryWriter writer) + { + _exe.SaveStateBinary(writer); + // other variables + writer.Write(Frame); + writer.Write(LagCount); + writer.Write(IsLagFrame); + writer.Write(BufferWidth); + writer.Write(BufferHeight); + } + + public byte[] SaveStateBinary() + { + var ms = new MemoryStream(); + var bw = new BinaryWriter(ms); + SaveStateBinary(bw); + bw.Flush(); + ms.Close(); + return ms.ToArray(); + } + + /// + /// called after the base core saves state. the core must save any other + /// variables that it needs to. + /// the default implementation does nothing + /// + /// + protected virtual void SaveStateBinaryInternal(BinaryWriter writer) + { + + } + + /// + /// called after the base core loads state. the core must load any other variables + /// that were in SaveStateBinaryInternal and reset any native pointers. + /// the default implementation does nothing + /// + /// + protected virtual void LoadStateBinaryInternal(BinaryReader reader) + { + + } + + #endregion + + #region ISoundProvider + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + samples = _soundBuffer; + nsamp = _numSamples; + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + + public void DiscardSamples() + { + } + + protected readonly short[] _soundBuffer; + protected int _numSamples; + public bool CanProvideAsync => false; + public SyncSoundMode SyncMode => SyncSoundMode.Sync; + + #endregion + + #region IVideoProvider + + public int[] GetVideoBuffer() + { + return _videoBuffer; + } + + protected readonly int[] _videoBuffer; + public virtual int VirtualWidth => BufferWidth; + public virtual int VirtualHeight => BufferWidth; + public int BufferWidth { get; private set; } + public int BufferHeight { get; private set; } + public virtual int VsyncNumerator { get; protected set; } + public virtual int VsyncDenominator { get; protected set; } + public int BackgroundColor => unchecked((int)0xff000000); + + #endregion + } +} diff --git a/output/dll/vb.wbx b/output/dll/vb.wbx deleted file mode 100644 index 562cb492b958dd1c6b797b66c40bfd61205fc209..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95246 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 diff --git a/output/dll/vb.wbx.gz b/output/dll/vb.wbx.gz new file mode 100644 index 0000000000000000000000000000000000000000..d76e6f75468ade5f231fbb9be10ed923034118a3 GIT binary patch literal 37597 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≥KPF)*1n#=P7_!y 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 literal 0 HcmV?d00001 diff --git a/waterbox/emulibc/waterboxcore.h b/waterbox/emulibc/waterboxcore.h new file mode 100644 index 0000000000..93621eab25 --- /dev/null +++ b/waterbox/emulibc/waterboxcore.h @@ -0,0 +1,39 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct +{ + uint32_t* VideoBuffer; + int16_t* SoundBuffer; + int64_t Cycles; + int32_t Width; + int32_t Height; + int32_t Samples; + int32_t Lagged; +} FrameInfo; + +typedef struct +{ + void* Data; + const char* Name; + int64_t Size; + int32_t Flags; +} MemoryArea; + +#define MEMORYAREA_FLAGS_WRITABLE 1 +#define MEMORYAREA_FLAGS_SAVERAMMABLE 2 +#define MEMORYAREA_FLAGS_ONEFILLED 4 +#define MEMORYAREA_FLAGS_PRIMARY 8 +#define MEMORYAREA_FLAGS_YUGEENDIAN 16 +#define MEMORYAREA_FLAGS_WORDSIZE1 32 +#define MEMORYAREA_FLAGS_WORDSIZE2 64 +#define MEMORYAREA_FLAGS_WORDSIZE4 128 +#define MEMORYAREA_FLAGS_WORDSIZE8 256 + +#ifdef __cplusplus +} +#endif diff --git a/waterbox/vb/vb.cpp b/waterbox/vb/vb.cpp index 5c8a46fdcf..82aec1e0fa 100644 --- a/waterbox/vb/vb.cpp +++ b/waterbox/vb/vb.cpp @@ -1,803 +1,795 @@ -/******************************************************************************/ -/* Mednafen Virtual Boy Emulation Module */ -/******************************************************************************/ -/* vb.cpp: -** Copyright (C) 2010-2017 Mednafen Team -** -** This program is free software; you can redistribute it and/or -** modify it under the terms of the GNU General Public License -** as published by the Free Software Foundation; either version 2 -** of the License, or (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software Foundation, Inc., -** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -#include "vb.h" -#include -#define EXPORT extern "C" ECL_EXPORT - -namespace MDFN_IEN_VB -{ -struct NativeSettings -{ - int InstantReadHack; - int DisableParallax; - int ThreeDeeMode; - int SwapViews; - int AnaglyphPreset; - int AnaglyphCustomLeftColor; - int AnaglyphCustomRightColor; - int NonAnaglyphColor; - int LedOnScale; - int InterlacePrescale; - int SideBySideSeparation; -}; - -static void (*input_callback)(); -static bool lagged; - -enum -{ - ANAGLYPH_PRESET_DISABLED = 0, - ANAGLYPH_PRESET_RED_BLUE, - ANAGLYPH_PRESET_RED_CYAN, - ANAGLYPH_PRESET_RED_ELECTRICCYAN, - ANAGLYPH_PRESET_RED_GREEN, - ANAGLYPH_PRESET_GREEN_MAGENTA, - ANAGLYPH_PRESET_YELLOW_BLUE, -}; - -static const uint32 AnaglyphPreset_Colors[][2] = -{ - {0, 0}, - {0xFF0000, 0x0000FF}, - {0xFF0000, 0x00B7EB}, - {0xFF0000, 0x00FFFF}, - {0xFF0000, 0x00FF00}, - {0x00FF00, 0xFF00FF}, - {0xFFFF00, 0x0000FF}, -}; - -int32 VB_InDebugPeek; - -static uint32 VB3DMode; - -static uint8 *WRAM = NULL; - -static uint8 *GPRAM = NULL; -static const uint32 GPRAM_Mask = 0xFFFF; - -static uint8 *GPROM = NULL; -static uint32 GPROM_Mask; - -V810 *VB_V810 = NULL; - -VSU *VB_VSU = NULL; -static uint32 VSU_CycleFix; - -static uint8 WCR; - -static int32 next_vip_ts, next_timer_ts, next_input_ts; - -static uint32 IRQ_Asserted; - -static INLINE void RecalcIntLevel(void) -{ - int ilevel = -1; - - for (int i = 4; i >= 0; i--) - { - if (IRQ_Asserted & (1 << i)) - { - ilevel = i; - break; - } - } - - VB_V810->SetInt(ilevel); -} - -void VBIRQ_Assert(int source, bool assert) -{ - assert(source >= 0 && source <= 4); - - IRQ_Asserted &= ~(1 << source); - - if (assert) - IRQ_Asserted |= 1 << source; - - RecalcIntLevel(); -} - -static MDFN_FASTCALL uint8 HWCTRL_Read(v810_timestamp_t ×tamp, uint32 A) -{ - uint8 ret = 0; - - if (A & 0x3) - { - //puts("HWCtrl Bogus Read?"); - return (ret); - } - - switch (A & 0xFF) - { - default: //printf("Unknown HWCTRL Read: %08x\n", A); - break; - - case 0x18: - case 0x1C: - case 0x20: - ret = TIMER_Read(timestamp, A); - break; - - case 0x24: - ret = WCR | 0xFC; - break; - - case 0x10: - case 0x14: - case 0x28: - lagged = false; - if (input_callback) - input_callback(); - ret = VBINPUT_Read(timestamp, A); - break; - } - - return (ret); -} - -static MDFN_FASTCALL void HWCTRL_Write(v810_timestamp_t ×tamp, uint32 A, uint8 V) -{ - if (A & 0x3) - { - puts("HWCtrl Bogus Write?"); - return; - } - - switch (A & 0xFF) - { - default: //printf("Unknown HWCTRL Write: %08x %02x\n", A, V); - break; - - case 0x18: - case 0x1C: - case 0x20: - TIMER_Write(timestamp, A, V); - break; - - case 0x24: - WCR = V & 0x3; - break; - - case 0x10: - case 0x14: - case 0x28: - VBINPUT_Write(timestamp, A, V); - break; - } -} - -uint8 MDFN_FASTCALL MemRead8(v810_timestamp_t ×tamp, uint32 A) -{ - uint8 ret = 0; - A &= (1 << 27) - 1; - - //if((A >> 24) <= 2) - // printf("Read8: %d %08x\n", timestamp, A); - - switch (A >> 24) - { - case 0: - ret = VIP_Read8(timestamp, A); - break; - - case 1: - break; - - case 2: - ret = HWCTRL_Read(timestamp, A); - break; - - case 3: - break; - case 4: - break; - - case 5: - ret = WRAM[A & 0xFFFF]; - break; - - case 6: - if (GPRAM) - ret = GPRAM[A & GPRAM_Mask]; - break; - - case 7: - ret = GPROM[A & GPROM_Mask]; - break; - } - return (ret); -} - -uint16 MDFN_FASTCALL MemRead16(v810_timestamp_t ×tamp, uint32 A) -{ - uint16 ret = 0; - - A &= (1 << 27) - 1; - - //if((A >> 24) <= 2) - // printf("Read16: %d %08x\n", timestamp, A); - - switch (A >> 24) - { - case 0: - ret = VIP_Read16(timestamp, A); - break; - - case 1: - break; - - case 2: - ret = HWCTRL_Read(timestamp, A); - break; - - case 3: - break; - - case 4: - break; - - case 5: - ret = MDFN_de16lsb(&WRAM[A & 0xFFFF]); - break; - - case 6: - if (GPRAM) - ret = MDFN_de16lsb(&GPRAM[A & GPRAM_Mask]); - break; - - case 7: - ret = MDFN_de16lsb(&GPROM[A & GPROM_Mask]); - break; - } - return ret; -} - -void MDFN_FASTCALL MemWrite8(v810_timestamp_t ×tamp, uint32 A, uint8 V) -{ - A &= (1 << 27) - 1; - - //if((A >> 24) <= 2) - // printf("Write8: %d %08x %02x\n", timestamp, A, V); - - switch (A >> 24) - { - case 0: - VIP_Write8(timestamp, A, V); - break; - - case 1: - VB_VSU->Write((timestamp + VSU_CycleFix) >> 2, A, V); - break; - - case 2: - HWCTRL_Write(timestamp, A, V); - break; - - case 3: - break; - - case 4: - break; - - case 5: - WRAM[A & 0xFFFF] = V; - break; - - case 6: - if (GPRAM) - GPRAM[A & GPRAM_Mask] = V; - break; - - case 7: // ROM, no writing allowed! - break; - } -} - -void MDFN_FASTCALL MemWrite16(v810_timestamp_t ×tamp, uint32 A, uint16 V) -{ - A &= (1 << 27) - 1; - - //if((A >> 24) <= 2) - // printf("Write16: %d %08x %04x\n", timestamp, A, V); - - switch (A >> 24) - { - case 0: - VIP_Write16(timestamp, A, V); - break; - - case 1: - VB_VSU->Write((timestamp + VSU_CycleFix) >> 2, A, V); - break; - - case 2: - HWCTRL_Write(timestamp, A, V); - break; - - case 3: - break; - - case 4: - break; - - case 5: - MDFN_en16lsb(&WRAM[A & 0xFFFF], V); - break; - - case 6: - if (GPRAM) - MDFN_en16lsb(&GPRAM[A & GPRAM_Mask], V); - break; - - case 7: // ROM, no writing allowed! - break; - } -} - -static void FixNonEvents(void) -{ - if (next_vip_ts & 0x40000000) - next_vip_ts = VB_EVENT_NONONO; - - if (next_timer_ts & 0x40000000) - next_timer_ts = VB_EVENT_NONONO; - - if (next_input_ts & 0x40000000) - next_input_ts = VB_EVENT_NONONO; -} - -static void EventReset(void) -{ - next_vip_ts = VB_EVENT_NONONO; - next_timer_ts = VB_EVENT_NONONO; - next_input_ts = VB_EVENT_NONONO; -} - -static INLINE int32 CalcNextTS(void) -{ - int32 next_timestamp = next_vip_ts; - - if (next_timestamp > next_timer_ts) - next_timestamp = next_timer_ts; - - if (next_timestamp > next_input_ts) - next_timestamp = next_input_ts; - - return (next_timestamp); -} - -static void RebaseTS(const v810_timestamp_t timestamp) -{ - //printf("Rebase: %08x %08x %08x\n", timestamp, next_vip_ts, next_timer_ts); - - assert(next_vip_ts > timestamp); - assert(next_timer_ts > timestamp); - assert(next_input_ts > timestamp); - - next_vip_ts -= timestamp; - next_timer_ts -= timestamp; - next_input_ts -= timestamp; -} - -void VB_SetEvent(const int type, const v810_timestamp_t next_timestamp) -{ - //assert(next_timestamp > VB_V810->v810_timestamp); - - if (type == VB_EVENT_VIP) - next_vip_ts = next_timestamp; - else if (type == VB_EVENT_TIMER) - next_timer_ts = next_timestamp; - else if (type == VB_EVENT_INPUT) - next_input_ts = next_timestamp; - - if (next_timestamp < VB_V810->GetEventNT()) - VB_V810->SetEventNT(next_timestamp); -} - -static int32 MDFN_FASTCALL EventHandler(const v810_timestamp_t timestamp) -{ - if (timestamp >= next_vip_ts) - next_vip_ts = VIP_Update(timestamp); - - if (timestamp >= next_timer_ts) - next_timer_ts = TIMER_Update(timestamp); - - if (timestamp >= next_input_ts) - next_input_ts = VBINPUT_Update(timestamp); - - return (CalcNextTS()); -} - -// Called externally from debug.cpp in some cases. -void ForceEventUpdates(const v810_timestamp_t timestamp) -{ - next_vip_ts = VIP_Update(timestamp); - next_timer_ts = TIMER_Update(timestamp); - next_input_ts = VBINPUT_Update(timestamp); - - VB_V810->SetEventNT(CalcNextTS()); - //printf("FEU: %d %d %d\n", next_vip_ts, next_timer_ts, next_input_ts); -} - -static void VB_Power(void) -{ - memset(WRAM, 0, 65536); - - VIP_Power(); - VB_VSU->Power(); - TIMER_Power(); - VBINPUT_Power(); - - EventReset(); - IRQ_Asserted = 0; - RecalcIntLevel(); - VB_V810->Reset(); - - VSU_CycleFix = 0; - WCR = 0; - - ForceEventUpdates(0); //VB_V810->v810_timestamp); -} - -/*struct VB_HeaderInfo -{ - char game_title[256]; - uint32 game_code; - uint16 manf_code; - uint8 version; -};*/ - -/*static void ReadHeader(const uint8 *const rom_data, const uint64 rom_size, VB_HeaderInfo *hi) -{ - iconv_t sjis_ict = iconv_open("UTF-8", "shift_jis"); - - if (sjis_ict != (iconv_t)-1) - { - char *in_ptr, *out_ptr; - size_t ibl, obl; - - ibl = 20; - obl = sizeof(hi->game_title) - 1; - - in_ptr = (char *)rom_data + (0xFFFFFDE0 & (rom_size - 1)); - out_ptr = hi->game_title; - - iconv(sjis_ict, (ICONV_CONST char **)&in_ptr, &ibl, &out_ptr, &obl); - iconv_close(sjis_ict); - - *out_ptr = 0; - - MDFN_zapctrlchars(hi->game_title); - MDFN_trim(hi->game_title); - } - else - hi->game_title[0] = 0; - - hi->game_code = MDFN_de32lsb(rom_data + (0xFFFFFDFB & (rom_size - 1))); - hi->manf_code = MDFN_de16lsb(rom_data + (0xFFFFFDF9 & (rom_size - 1))); - hi->version = rom_data[0xFFFFFDFF & (rom_size - 1)]; -}*/ - -void VB_ExitLoop(void) -{ - VB_V810->Exit(); -} - - -/*MDFNGI EmulatedVB = - { - - PortInfo, - Load, - TestMagic, - NULL, - NULL, - CloseGame, - - SetLayerEnableMask, - NULL, // Layer names, null-delimited - - NULL, - NULL, - - VIP_CPInfo, - 1 << 0, - - CheatInfo_Empty, - - false, - StateAction, - Emulate, - NULL, - VBINPUT_SetInput, - NULL, - DoSimpleCommand, - NULL, - VBSettings, - MDFN_MASTERCLOCK_FIXED(VB_MASTER_CLOCK), - 0, - false, // Multires possible? - - 0, // lcm_width - 0, // lcm_height - NULL, // Dummy - - 384, // Nominal width - 224, // Nominal height - - 384, // Framebuffer width - 256, // Framebuffer height - - 2, // Number of output sound channels -};*/ -} - -using namespace MDFN_IEN_VB; - -EXPORT int Load(const uint8 *rom, int length, const NativeSettings* settings) -{ - const uint64 rom_size = length; - V810_Emu_Mode cpu_mode = V810_EMU_MODE_ACCURATE; - - VB_InDebugPeek = 0; - - if (rom_size != round_up_pow2(rom_size)) - { - return 0; - // throw MDFN_Error(0, _("VB ROM image size is not a power of 2.")); - } - - if (rom_size < 256) - { - return 0; - //throw MDFN_Error(0, _("VB ROM image size is too small.")); - } - - if (rom_size > (1 << 24)) - { - return 0; - //throw MDFN_Error(0, _("VB ROM image size is too large.")); - } - - VB_V810 = new V810(); - VB_V810->Init(cpu_mode, true); - - VB_V810->SetMemReadHandlers(MemRead8, MemRead16, NULL); - VB_V810->SetMemWriteHandlers(MemWrite8, MemWrite16, NULL); - - VB_V810->SetIOReadHandlers(MemRead8, MemRead16, NULL); - VB_V810->SetIOWriteHandlers(MemWrite8, MemWrite16, NULL); - - for (int i = 0; i < 256; i++) - { - VB_V810->SetMemReadBus32(i, false); - VB_V810->SetMemWriteBus32(i, false); - } - - std::vector Map_Addresses; - - for (uint64 A = 0; A < 1ULL << 32; A += (1 << 27)) - { - for (uint64 sub_A = 5 << 24; sub_A < (6 << 24); sub_A += 65536) - { - Map_Addresses.push_back(A + sub_A); - } - } - - WRAM = VB_V810->SetFastMap(alloc_plain, &Map_Addresses[0], 65536, Map_Addresses.size(), "WRAM"); - Map_Addresses.clear(); - - // Round up the ROM size to 65536(we mirror it a little later) - GPROM_Mask = (rom_size < 65536) ? (65536 - 1) : (rom_size - 1); - - for (uint64 A = 0; A < 1ULL << 32; A += (1 << 27)) - { - for (uint64 sub_A = 7 << 24; sub_A < (8 << 24); sub_A += GPROM_Mask + 1) - { - Map_Addresses.push_back(A + sub_A); - //printf("%08x\n", (uint32)(A + sub_A)); - } - } - - GPROM = VB_V810->SetFastMap(alloc_sealed, &Map_Addresses[0], GPROM_Mask + 1, Map_Addresses.size(), "Cart ROM"); - Map_Addresses.clear(); - - memcpy(GPROM, rom, rom_size); - - // Mirror ROM images < 64KiB to 64KiB - for (uint64 i = rom_size; i < 65536; i += rom_size) - { - memcpy(GPROM + i, GPROM, rom_size); - } - - /*VB_HeaderInfo hinfo; - - ReadHeader(GPROM, rom_size, &hinfo); - - MDFN_printf(_("Title: %s\n"), hinfo.game_title); - MDFN_printf(_("Game ID Code: %u\n"), hinfo.game_code); - MDFN_printf(_("Manufacturer Code: %d\n"), hinfo.manf_code); - MDFN_printf(_("Version: %u\n"), hinfo.version); - - MDFN_printf(_("ROM: %uKiB\n"), (unsigned)(rom_size / 1024)); - MDFN_printf(_("ROM MD5: 0x%s\n"), md5_context::asciistr(MDFNGameInfo->MD5, 0).c_str());*/ - - /*MDFN_printf("\n"); - - MDFN_printf(_("V810 Emulation Mode: %s\n"), (cpu_mode == V810_EMU_MODE_ACCURATE) ? _("Accurate") : _("Fast"));*/ - - for (uint64 A = 0; A < 1ULL << 32; A += (1 << 27)) - { - for (uint64 sub_A = 6 << 24; sub_A < (7 << 24); sub_A += GPRAM_Mask + 1) - { - //printf("GPRAM: %08x\n", A + sub_A); - Map_Addresses.push_back(A + sub_A); - } - } - - GPRAM = VB_V810->SetFastMap(alloc_plain, &Map_Addresses[0], GPRAM_Mask + 1, Map_Addresses.size(), "Cart RAM"); - Map_Addresses.clear(); - - memset(GPRAM, 0, GPRAM_Mask + 1); - - VIP_Init(); - VB_VSU = new VSU(); - VBINPUT_Init(); - - VB3DMode = settings->ThreeDeeMode; - uint32 prescale = settings->InterlacePrescale; - uint32 sbs_separation = settings->SideBySideSeparation; - bool reverse = settings->SwapViews; - - VIP_Set3DMode(VB3DMode, reverse, prescale, sbs_separation); - - VIP_SetParallaxDisable(settings->DisableParallax); - - { - auto presetColor = settings->AnaglyphPreset; - - uint32 lcolor = settings->AnaglyphCustomLeftColor; - uint32 rcolor = settings->AnaglyphCustomRightColor; - - if (presetColor != ANAGLYPH_PRESET_DISABLED) - { - lcolor = AnaglyphPreset_Colors[presetColor][0]; - rcolor = AnaglyphPreset_Colors[presetColor][1]; - } - VIP_SetAnaglyphColors(lcolor, rcolor); - VIP_SetDefaultColor(settings->NonAnaglyphColor); - } - - VBINPUT_SetInstantReadHack(settings->InstantReadHack); - - VIP_SetLEDOnScale(settings->LedOnScale / 1000.0); - - VB_Power(); - - /*switch (VB3DMode) - { - default: - break; - - case VB3DMODE_VLI: - MDFNGameInfo->nominal_width = 768 * prescale; - MDFNGameInfo->nominal_height = 224; - MDFNGameInfo->fb_width = 768 * prescale; - MDFNGameInfo->fb_height = 224; - break; - - case VB3DMODE_HLI: - MDFNGameInfo->nominal_width = 384; - MDFNGameInfo->nominal_height = 448 * prescale; - MDFNGameInfo->fb_width = 384; - MDFNGameInfo->fb_height = 448 * prescale; - break; - - case VB3DMODE_CSCOPE: - MDFNGameInfo->nominal_width = 512; - MDFNGameInfo->nominal_height = 384; - MDFNGameInfo->fb_width = 512; - MDFNGameInfo->fb_height = 384; - break; - - case VB3DMODE_SIDEBYSIDE: - MDFNGameInfo->nominal_width = 384 * 2 + sbs_separation; - MDFNGameInfo->nominal_height = 224; - MDFNGameInfo->fb_width = 384 * 2 + sbs_separation; - MDFNGameInfo->fb_height = 224; - break; - } - MDFNGameInfo->lcm_width = MDFNGameInfo->fb_width; - MDFNGameInfo->lcm_height = MDFNGameInfo->fb_height;*/ - - VB_VSU->SetSoundRate(44100); - - return 1; -} - -EXPORT void GetMemoryArea(int which, void **ptr, int *size) -{ - switch (which) - { - case 0: - *ptr = WRAM; - *size = 65536; - break; - case 1: - *ptr = GPRAM; - *size = GPRAM_Mask + 1; - break; - case 2: - *ptr = GPROM; - *size = GPROM_Mask + 1; - break; - default: - *ptr = nullptr; - *size = 0; - break; - } -} - -EXPORT void Emulate(EmulateSpecStruct *espec) -{ - v810_timestamp_t v810_timestamp; - lagged = true; - - VBINPUT_Frame(&espec->Buttons); - - VIP_StartFrame(espec); - - v810_timestamp = VB_V810->Run(EventHandler); - - FixNonEvents(); - ForceEventUpdates(v810_timestamp); - - espec->SoundBufSize = VB_VSU->EndFrame((v810_timestamp + VSU_CycleFix) >> 2, espec->SoundBuf, espec->SoundBufMaxSize); - - VSU_CycleFix = (v810_timestamp + VSU_CycleFix) & 3; - - espec->MasterCycles = v810_timestamp; - espec->Lagged = lagged; - - TIMER_ResetTS(); - VBINPUT_ResetTS(); - VIP_ResetTS(); - - RebaseTS(v810_timestamp); - - VB_V810->ResetTS(0); -} - -EXPORT void HardReset() -{ - VB_Power(); -} - -EXPORT void SetInputCallback(void (*callback)()) -{ - input_callback = callback; -} - -int main() -{ - return 0; -} +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* vb.cpp: +** Copyright (C) 2010-2017 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "vb.h" +#include "../emulibc/emulibc.h" +#include "../emulibc/waterboxcore.h" +#define EXPORT extern "C" ECL_EXPORT + +namespace MDFN_IEN_VB +{ +struct NativeSettings +{ + int InstantReadHack; + int DisableParallax; + int ThreeDeeMode; + int SwapViews; + int AnaglyphPreset; + int AnaglyphCustomLeftColor; + int AnaglyphCustomRightColor; + int NonAnaglyphColor; + int LedOnScale; + int InterlacePrescale; + int SideBySideSeparation; +}; + +static void (*input_callback)(); +static bool lagged; + +enum +{ + ANAGLYPH_PRESET_DISABLED = 0, + ANAGLYPH_PRESET_RED_BLUE, + ANAGLYPH_PRESET_RED_CYAN, + ANAGLYPH_PRESET_RED_ELECTRICCYAN, + ANAGLYPH_PRESET_RED_GREEN, + ANAGLYPH_PRESET_GREEN_MAGENTA, + ANAGLYPH_PRESET_YELLOW_BLUE, +}; + +static const uint32 AnaglyphPreset_Colors[][2] = +{ + {0, 0}, + {0xFF0000, 0x0000FF}, + {0xFF0000, 0x00B7EB}, + {0xFF0000, 0x00FFFF}, + {0xFF0000, 0x00FF00}, + {0x00FF00, 0xFF00FF}, + {0xFFFF00, 0x0000FF}, +}; + +static uint32 VB3DMode; + +static uint8 *WRAM = NULL; + +static uint8 *GPRAM = NULL; +static const uint32 GPRAM_Mask = 0xFFFF; + +static uint8 *GPROM = NULL; +static uint32 GPROM_Mask; + +V810 *VB_V810 = NULL; + +VSU *VB_VSU = NULL; +static uint32 VSU_CycleFix; + +static uint8 WCR; + +static int32 next_vip_ts, next_timer_ts, next_input_ts; + +static uint32 IRQ_Asserted; + +static INLINE void RecalcIntLevel(void) +{ + int ilevel = -1; + + for (int i = 4; i >= 0; i--) + { + if (IRQ_Asserted & (1 << i)) + { + ilevel = i; + break; + } + } + + VB_V810->SetInt(ilevel); +} + +void VBIRQ_Assert(int source, bool assert) +{ + assert(source >= 0 && source <= 4); + + IRQ_Asserted &= ~(1 << source); + + if (assert) + IRQ_Asserted |= 1 << source; + + RecalcIntLevel(); +} + +static MDFN_FASTCALL uint8 HWCTRL_Read(v810_timestamp_t ×tamp, uint32 A) +{ + uint8 ret = 0; + + if (A & 0x3) + { + //puts("HWCtrl Bogus Read?"); + return (ret); + } + + switch (A & 0xFF) + { + default: //printf("Unknown HWCTRL Read: %08x\n", A); + break; + + case 0x18: + case 0x1C: + case 0x20: + ret = TIMER_Read(timestamp, A); + break; + + case 0x24: + ret = WCR | 0xFC; + break; + + case 0x10: + case 0x14: + case 0x28: + lagged = false; + if (input_callback) + input_callback(); + ret = VBINPUT_Read(timestamp, A); + break; + } + + return (ret); +} + +static MDFN_FASTCALL void HWCTRL_Write(v810_timestamp_t ×tamp, uint32 A, uint8 V) +{ + if (A & 0x3) + { + puts("HWCtrl Bogus Write?"); + return; + } + + switch (A & 0xFF) + { + default: //printf("Unknown HWCTRL Write: %08x %02x\n", A, V); + break; + + case 0x18: + case 0x1C: + case 0x20: + TIMER_Write(timestamp, A, V); + break; + + case 0x24: + WCR = V & 0x3; + break; + + case 0x10: + case 0x14: + case 0x28: + VBINPUT_Write(timestamp, A, V); + break; + } +} + +uint8 MDFN_FASTCALL MemRead8(v810_timestamp_t ×tamp, uint32 A) +{ + uint8 ret = 0; + A &= (1 << 27) - 1; + + //if((A >> 24) <= 2) + // printf("Read8: %d %08x\n", timestamp, A); + + switch (A >> 24) + { + case 0: + ret = VIP_Read8(timestamp, A); + break; + + case 1: + break; + + case 2: + ret = HWCTRL_Read(timestamp, A); + break; + + case 3: + break; + case 4: + break; + + case 5: + ret = WRAM[A & 0xFFFF]; + break; + + case 6: + if (GPRAM) + ret = GPRAM[A & GPRAM_Mask]; + break; + + case 7: + ret = GPROM[A & GPROM_Mask]; + break; + } + return (ret); +} + +uint16 MDFN_FASTCALL MemRead16(v810_timestamp_t ×tamp, uint32 A) +{ + uint16 ret = 0; + + A &= (1 << 27) - 1; + + //if((A >> 24) <= 2) + // printf("Read16: %d %08x\n", timestamp, A); + + switch (A >> 24) + { + case 0: + ret = VIP_Read16(timestamp, A); + break; + + case 1: + break; + + case 2: + ret = HWCTRL_Read(timestamp, A); + break; + + case 3: + break; + + case 4: + break; + + case 5: + ret = MDFN_de16lsb(&WRAM[A & 0xFFFF]); + break; + + case 6: + if (GPRAM) + ret = MDFN_de16lsb(&GPRAM[A & GPRAM_Mask]); + break; + + case 7: + ret = MDFN_de16lsb(&GPROM[A & GPROM_Mask]); + break; + } + return ret; +} + +void MDFN_FASTCALL MemWrite8(v810_timestamp_t ×tamp, uint32 A, uint8 V) +{ + A &= (1 << 27) - 1; + + //if((A >> 24) <= 2) + // printf("Write8: %d %08x %02x\n", timestamp, A, V); + + switch (A >> 24) + { + case 0: + VIP_Write8(timestamp, A, V); + break; + + case 1: + VB_VSU->Write((timestamp + VSU_CycleFix) >> 2, A, V); + break; + + case 2: + HWCTRL_Write(timestamp, A, V); + break; + + case 3: + break; + + case 4: + break; + + case 5: + WRAM[A & 0xFFFF] = V; + break; + + case 6: + if (GPRAM) + GPRAM[A & GPRAM_Mask] = V; + break; + + case 7: // ROM, no writing allowed! + break; + } +} + +void MDFN_FASTCALL MemWrite16(v810_timestamp_t ×tamp, uint32 A, uint16 V) +{ + A &= (1 << 27) - 1; + + //if((A >> 24) <= 2) + // printf("Write16: %d %08x %04x\n", timestamp, A, V); + + switch (A >> 24) + { + case 0: + VIP_Write16(timestamp, A, V); + break; + + case 1: + VB_VSU->Write((timestamp + VSU_CycleFix) >> 2, A, V); + break; + + case 2: + HWCTRL_Write(timestamp, A, V); + break; + + case 3: + break; + + case 4: + break; + + case 5: + MDFN_en16lsb(&WRAM[A & 0xFFFF], V); + break; + + case 6: + if (GPRAM) + MDFN_en16lsb(&GPRAM[A & GPRAM_Mask], V); + break; + + case 7: // ROM, no writing allowed! + break; + } +} + +static void FixNonEvents(void) +{ + if (next_vip_ts & 0x40000000) + next_vip_ts = VB_EVENT_NONONO; + + if (next_timer_ts & 0x40000000) + next_timer_ts = VB_EVENT_NONONO; + + if (next_input_ts & 0x40000000) + next_input_ts = VB_EVENT_NONONO; +} + +static void EventReset(void) +{ + next_vip_ts = VB_EVENT_NONONO; + next_timer_ts = VB_EVENT_NONONO; + next_input_ts = VB_EVENT_NONONO; +} + +static INLINE int32 CalcNextTS(void) +{ + int32 next_timestamp = next_vip_ts; + + if (next_timestamp > next_timer_ts) + next_timestamp = next_timer_ts; + + if (next_timestamp > next_input_ts) + next_timestamp = next_input_ts; + + return (next_timestamp); +} + +static void RebaseTS(const v810_timestamp_t timestamp) +{ + //printf("Rebase: %08x %08x %08x\n", timestamp, next_vip_ts, next_timer_ts); + + assert(next_vip_ts > timestamp); + assert(next_timer_ts > timestamp); + assert(next_input_ts > timestamp); + + next_vip_ts -= timestamp; + next_timer_ts -= timestamp; + next_input_ts -= timestamp; +} + +void VB_SetEvent(const int type, const v810_timestamp_t next_timestamp) +{ + //assert(next_timestamp > VB_V810->v810_timestamp); + + if (type == VB_EVENT_VIP) + next_vip_ts = next_timestamp; + else if (type == VB_EVENT_TIMER) + next_timer_ts = next_timestamp; + else if (type == VB_EVENT_INPUT) + next_input_ts = next_timestamp; + + if (next_timestamp < VB_V810->GetEventNT()) + VB_V810->SetEventNT(next_timestamp); +} + +static int32 MDFN_FASTCALL EventHandler(const v810_timestamp_t timestamp) +{ + if (timestamp >= next_vip_ts) + next_vip_ts = VIP_Update(timestamp); + + if (timestamp >= next_timer_ts) + next_timer_ts = TIMER_Update(timestamp); + + if (timestamp >= next_input_ts) + next_input_ts = VBINPUT_Update(timestamp); + + return (CalcNextTS()); +} + +// Called externally from debug.cpp in some cases. +void ForceEventUpdates(const v810_timestamp_t timestamp) +{ + next_vip_ts = VIP_Update(timestamp); + next_timer_ts = TIMER_Update(timestamp); + next_input_ts = VBINPUT_Update(timestamp); + + VB_V810->SetEventNT(CalcNextTS()); + //printf("FEU: %d %d %d\n", next_vip_ts, next_timer_ts, next_input_ts); +} + +static void VB_Power(void) +{ + memset(WRAM, 0, 65536); + + VIP_Power(); + VB_VSU->Power(); + TIMER_Power(); + VBINPUT_Power(); + + EventReset(); + IRQ_Asserted = 0; + RecalcIntLevel(); + VB_V810->Reset(); + + VSU_CycleFix = 0; + WCR = 0; + + ForceEventUpdates(0); //VB_V810->v810_timestamp); +} + +/*struct VB_HeaderInfo +{ + char game_title[256]; + uint32 game_code; + uint16 manf_code; + uint8 version; +};*/ + +/*static void ReadHeader(const uint8 *const rom_data, const uint64 rom_size, VB_HeaderInfo *hi) +{ + iconv_t sjis_ict = iconv_open("UTF-8", "shift_jis"); + + if (sjis_ict != (iconv_t)-1) + { + char *in_ptr, *out_ptr; + size_t ibl, obl; + + ibl = 20; + obl = sizeof(hi->game_title) - 1; + + in_ptr = (char *)rom_data + (0xFFFFFDE0 & (rom_size - 1)); + out_ptr = hi->game_title; + + iconv(sjis_ict, (ICONV_CONST char **)&in_ptr, &ibl, &out_ptr, &obl); + iconv_close(sjis_ict); + + *out_ptr = 0; + + MDFN_zapctrlchars(hi->game_title); + MDFN_trim(hi->game_title); + } + else + hi->game_title[0] = 0; + + hi->game_code = MDFN_de32lsb(rom_data + (0xFFFFFDFB & (rom_size - 1))); + hi->manf_code = MDFN_de16lsb(rom_data + (0xFFFFFDF9 & (rom_size - 1))); + hi->version = rom_data[0xFFFFFDFF & (rom_size - 1)]; +}*/ + +void VB_ExitLoop(void) +{ + VB_V810->Exit(); +} + + +/*MDFNGI EmulatedVB = + { + + PortInfo, + Load, + TestMagic, + NULL, + NULL, + CloseGame, + + SetLayerEnableMask, + NULL, // Layer names, null-delimited + + NULL, + NULL, + + VIP_CPInfo, + 1 << 0, + + CheatInfo_Empty, + + false, + StateAction, + Emulate, + NULL, + VBINPUT_SetInput, + NULL, + DoSimpleCommand, + NULL, + VBSettings, + MDFN_MASTERCLOCK_FIXED(VB_MASTER_CLOCK), + 0, + false, // Multires possible? + + 0, // lcm_width + 0, // lcm_height + NULL, // Dummy + + 384, // Nominal width + 224, // Nominal height + + 384, // Framebuffer width + 256, // Framebuffer height + + 2, // Number of output sound channels +};*/ +} + +using namespace MDFN_IEN_VB; + +EXPORT int Load(const uint8 *rom, int length, const NativeSettings* settings) +{ + const uint64 rom_size = length; + V810_Emu_Mode cpu_mode = V810_EMU_MODE_ACCURATE; + + if (rom_size != round_up_pow2(rom_size)) + { + return 0; + // throw MDFN_Error(0, _("VB ROM image size is not a power of 2.")); + } + + if (rom_size < 256) + { + return 0; + //throw MDFN_Error(0, _("VB ROM image size is too small.")); + } + + if (rom_size > (1 << 24)) + { + return 0; + //throw MDFN_Error(0, _("VB ROM image size is too large.")); + } + + VB_V810 = new V810(); + VB_V810->Init(cpu_mode, true); + + VB_V810->SetMemReadHandlers(MemRead8, MemRead16, NULL); + VB_V810->SetMemWriteHandlers(MemWrite8, MemWrite16, NULL); + + VB_V810->SetIOReadHandlers(MemRead8, MemRead16, NULL); + VB_V810->SetIOWriteHandlers(MemWrite8, MemWrite16, NULL); + + for (int i = 0; i < 256; i++) + { + VB_V810->SetMemReadBus32(i, false); + VB_V810->SetMemWriteBus32(i, false); + } + + std::vector Map_Addresses; + + for (uint64 A = 0; A < 1ULL << 32; A += (1 << 27)) + { + for (uint64 sub_A = 5 << 24; sub_A < (6 << 24); sub_A += 65536) + { + Map_Addresses.push_back(A + sub_A); + } + } + + WRAM = VB_V810->SetFastMap(alloc_plain, &Map_Addresses[0], 65536, Map_Addresses.size(), "WRAM"); + Map_Addresses.clear(); + + // Round up the ROM size to 65536(we mirror it a little later) + GPROM_Mask = (rom_size < 65536) ? (65536 - 1) : (rom_size - 1); + + for (uint64 A = 0; A < 1ULL << 32; A += (1 << 27)) + { + for (uint64 sub_A = 7 << 24; sub_A < (8 << 24); sub_A += GPROM_Mask + 1) + { + Map_Addresses.push_back(A + sub_A); + //printf("%08x\n", (uint32)(A + sub_A)); + } + } + + GPROM = VB_V810->SetFastMap(alloc_sealed, &Map_Addresses[0], GPROM_Mask + 1, Map_Addresses.size(), "Cart ROM"); + Map_Addresses.clear(); + + memcpy(GPROM, rom, rom_size); + + // Mirror ROM images < 64KiB to 64KiB + for (uint64 i = rom_size; i < 65536; i += rom_size) + { + memcpy(GPROM + i, GPROM, rom_size); + } + + /*VB_HeaderInfo hinfo; + + ReadHeader(GPROM, rom_size, &hinfo); + + MDFN_printf(_("Title: %s\n"), hinfo.game_title); + MDFN_printf(_("Game ID Code: %u\n"), hinfo.game_code); + MDFN_printf(_("Manufacturer Code: %d\n"), hinfo.manf_code); + MDFN_printf(_("Version: %u\n"), hinfo.version); + + MDFN_printf(_("ROM: %uKiB\n"), (unsigned)(rom_size / 1024)); + MDFN_printf(_("ROM MD5: 0x%s\n"), md5_context::asciistr(MDFNGameInfo->MD5, 0).c_str());*/ + + /*MDFN_printf("\n"); + + MDFN_printf(_("V810 Emulation Mode: %s\n"), (cpu_mode == V810_EMU_MODE_ACCURATE) ? _("Accurate") : _("Fast"));*/ + + for (uint64 A = 0; A < 1ULL << 32; A += (1 << 27)) + { + for (uint64 sub_A = 6 << 24; sub_A < (7 << 24); sub_A += GPRAM_Mask + 1) + { + //printf("GPRAM: %08x\n", A + sub_A); + Map_Addresses.push_back(A + sub_A); + } + } + + GPRAM = VB_V810->SetFastMap(alloc_plain, &Map_Addresses[0], GPRAM_Mask + 1, Map_Addresses.size(), "Cart RAM"); + Map_Addresses.clear(); + + memset(GPRAM, 0, GPRAM_Mask + 1); + + VIP_Init(); + VB_VSU = new VSU(); + VBINPUT_Init(); + + VB3DMode = settings->ThreeDeeMode; + uint32 prescale = settings->InterlacePrescale; + uint32 sbs_separation = settings->SideBySideSeparation; + bool reverse = settings->SwapViews; + + VIP_Set3DMode(VB3DMode, reverse, prescale, sbs_separation); + + VIP_SetParallaxDisable(settings->DisableParallax); + + { + auto presetColor = settings->AnaglyphPreset; + + uint32 lcolor = settings->AnaglyphCustomLeftColor; + uint32 rcolor = settings->AnaglyphCustomRightColor; + + if (presetColor != ANAGLYPH_PRESET_DISABLED) + { + lcolor = AnaglyphPreset_Colors[presetColor][0]; + rcolor = AnaglyphPreset_Colors[presetColor][1]; + } + VIP_SetAnaglyphColors(lcolor, rcolor); + VIP_SetDefaultColor(settings->NonAnaglyphColor); + } + + VBINPUT_SetInstantReadHack(settings->InstantReadHack); + + VIP_SetLEDOnScale(settings->LedOnScale / 1000.0); + + VB_Power(); + + /*switch (VB3DMode) + { + default: + break; + + case VB3DMODE_VLI: + MDFNGameInfo->nominal_width = 768 * prescale; + MDFNGameInfo->nominal_height = 224; + MDFNGameInfo->fb_width = 768 * prescale; + MDFNGameInfo->fb_height = 224; + break; + + case VB3DMODE_HLI: + MDFNGameInfo->nominal_width = 384; + MDFNGameInfo->nominal_height = 448 * prescale; + MDFNGameInfo->fb_width = 384; + MDFNGameInfo->fb_height = 448 * prescale; + break; + + case VB3DMODE_CSCOPE: + MDFNGameInfo->nominal_width = 512; + MDFNGameInfo->nominal_height = 384; + MDFNGameInfo->fb_width = 512; + MDFNGameInfo->fb_height = 384; + break; + + case VB3DMODE_SIDEBYSIDE: + MDFNGameInfo->nominal_width = 384 * 2 + sbs_separation; + MDFNGameInfo->nominal_height = 224; + MDFNGameInfo->fb_width = 384 * 2 + sbs_separation; + MDFNGameInfo->fb_height = 224; + break; + } + MDFNGameInfo->lcm_width = MDFNGameInfo->fb_width; + MDFNGameInfo->lcm_height = MDFNGameInfo->fb_height;*/ + + VB_VSU->SetSoundRate(44100); + + return 1; +} + +EXPORT void GetMemoryAreas(MemoryArea* m) +{ + m[0].Data = WRAM; + m[0].Name = "WRAM"; + m[0].Size = 65536; + m[0].Flags = MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_PRIMARY | MEMORYAREA_FLAGS_WORDSIZE4; + + m[1].Data = GPRAM; + m[1].Name = "CARTRAM"; + m[1].Size = GPRAM_Mask + 1; + m[1].Flags = MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_SAVERAMMABLE | MEMORYAREA_FLAGS_WORDSIZE4; + + m[2].Data = GPROM; + m[2].Name = "ROM"; + m[2].Size = GPROM_Mask + 1; + m[2].Flags = MEMORYAREA_FLAGS_WORDSIZE4; +} + +EXPORT void FrameAdvance(MyFrameInfo* frame) +{ + v810_timestamp_t v810_timestamp; + lagged = true; + + VBINPUT_Frame(&frame->Buttons); + + VIP_StartFrame(frame); + + v810_timestamp = VB_V810->Run(EventHandler); + + FixNonEvents(); + ForceEventUpdates(v810_timestamp); + + frame->Samples = VB_VSU->EndFrame((v810_timestamp + VSU_CycleFix) >> 2, frame->SoundBuffer, 8192); + + VSU_CycleFix = (v810_timestamp + VSU_CycleFix) & 3; + + frame->Cycles = v810_timestamp; + frame->Lagged = lagged; + + TIMER_ResetTS(); + VBINPUT_ResetTS(); + VIP_ResetTS(); + + RebaseTS(v810_timestamp); + + VB_V810->ResetTS(0); +} + +EXPORT void HardReset() +{ + VB_Power(); +} + +EXPORT void SetInputCallback(void (*callback)()) +{ + input_callback = callback; +} + +int main() +{ + return 0; +} diff --git a/waterbox/vb/vb.h b/waterbox/vb/vb.h index 8b26411e72..8ca976ddaf 100644 --- a/waterbox/vb/vb.h +++ b/waterbox/vb/vb.h @@ -1,114 +1,124 @@ -/******************************************************************************/ -/* Mednafen Virtual Boy Emulation Module */ -/******************************************************************************/ -/* vb.h: -** Copyright (C) 2010-2016 Mednafen Team -** -** This program is free software; you can redistribute it and/or -** modify it under the terms of the GNU General Public License -** as published by the Free Software Foundation; either version 2 -** of the License, or (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software Foundation, Inc., -** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -typedef uint8_t uint8; -typedef uint16_t uint16; -typedef uint32_t uint32; -typedef uint64_t uint64; -typedef int8_t int8; -typedef int16_t int16; -typedef int32_t int32; -typedef int64_t int64; - -#define MDFN_FASTCALL -#define INLINE inline -#define MDFN_COLD -#define NO_INLINE -//#define MDFN_ASSUME_ALIGNED(p, align) ((decltype(p))__builtin_assume_aligned((p), (align))) -#define MDFN_ASSUME_ALIGNED(p, align) (p) -#define trio_snprintf snprintf -#define TRUE true -#define FALSE false -#ifndef __alignas_is_defined -#define alignas(p) -#endif - -#include "endian.h" -#include "math_ops.h" -#include "blip/Blip_Buffer.h" -#include "v810/v810_fp_ops.h" -#include "v810/v810_cpu.h" - -#include "git.h" - -#include "vsu.h" -#include "vip.h" -#include "timer.h" -#include "input.h" - - -namespace MDFN_IEN_VB -{ - -enum -{ - VB3DMODE_ANAGLYPH = 0, - VB3DMODE_CSCOPE = 1, - VB3DMODE_SIDEBYSIDE = 2, - VB3DMODE_OVERUNDER = 3, - VB3DMODE_VLI, - VB3DMODE_HLI -}; - -#define VB_MASTER_CLOCK 20000000.0 - -enum -{ - VB_EVENT_VIP = 0, - VB_EVENT_TIMER, - VB_EVENT_INPUT, - // VB_EVENT_COMM -}; - -#define VB_EVENT_NONONO 0x7fffffff - -void VB_SetEvent(const int type, const v810_timestamp_t next_timestamp); - -#define VBIRQ_SOURCE_INPUT 0 -#define VBIRQ_SOURCE_TIMER 1 -#define VBIRQ_SOURCE_EXPANSION 2 -#define VBIRQ_SOURCE_COMM 3 -#define VBIRQ_SOURCE_VIP 4 - -void VBIRQ_Assert(int source, bool assert); - -void VB_ExitLoop(void); - -void ForceEventUpdates(const v810_timestamp_t timestamp); - -uint8 MDFN_FASTCALL MemRead8(v810_timestamp_t ×tamp, uint32 A); -uint16 MDFN_FASTCALL MemRead16(v810_timestamp_t ×tamp, uint32 A); - -void MDFN_FASTCALL MemWrite8(v810_timestamp_t ×tamp, uint32 A, uint8 V); -void MDFN_FASTCALL MemWrite16(v810_timestamp_t ×tamp, uint32 A, uint16 V); - -extern int32 VB_InDebugPeek; -} +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* vb.h: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef uint64_t uint64; +typedef int8_t int8; +typedef int16_t int16; +typedef int32_t int32; +typedef int64_t int64; + +#define MDFN_FASTCALL +#define INLINE inline +#define MDFN_COLD +#define NO_INLINE +//#define MDFN_ASSUME_ALIGNED(p, align) ((decltype(p))__builtin_assume_aligned((p), (align))) +#define MDFN_ASSUME_ALIGNED(p, align) (p) +#define trio_snprintf snprintf +#define TRUE true +#define FALSE false +#ifndef __alignas_is_defined +#define alignas(p) +#endif + +struct MyFrameInfo +{ + uint32_t* VideoBuffer; + int16_t* SoundBuffer; + int64_t Cycles; + int32_t Width; + int32_t Height; + int32_t Samples; + int32_t Lagged; + int32_t Buttons; +}; + +#include "endian.h" +#include "math_ops.h" +#include "blip/Blip_Buffer.h" +#include "v810/v810_fp_ops.h" +#include "v810/v810_cpu.h" + +#include "git.h" + +#include "vsu.h" +#include "vip.h" +#include "timer.h" +#include "input.h" + + +namespace MDFN_IEN_VB +{ + +enum +{ + VB3DMODE_ANAGLYPH = 0, + VB3DMODE_CSCOPE = 1, + VB3DMODE_SIDEBYSIDE = 2, + VB3DMODE_OVERUNDER = 3, + VB3DMODE_VLI, + VB3DMODE_HLI +}; + +#define VB_MASTER_CLOCK 20000000.0 + +enum +{ + VB_EVENT_VIP = 0, + VB_EVENT_TIMER, + VB_EVENT_INPUT, + // VB_EVENT_COMM +}; + +#define VB_EVENT_NONONO 0x7fffffff + +void VB_SetEvent(const int type, const v810_timestamp_t next_timestamp); + +#define VBIRQ_SOURCE_INPUT 0 +#define VBIRQ_SOURCE_TIMER 1 +#define VBIRQ_SOURCE_EXPANSION 2 +#define VBIRQ_SOURCE_COMM 3 +#define VBIRQ_SOURCE_VIP 4 + +void VBIRQ_Assert(int source, bool assert); + +void VB_ExitLoop(void); + +void ForceEventUpdates(const v810_timestamp_t timestamp); + +uint8 MDFN_FASTCALL MemRead8(v810_timestamp_t ×tamp, uint32 A); +uint16 MDFN_FASTCALL MemRead16(v810_timestamp_t ×tamp, uint32 A); + +void MDFN_FASTCALL MemWrite8(v810_timestamp_t ×tamp, uint32 A, uint8 V); +void MDFN_FASTCALL MemWrite16(v810_timestamp_t ×tamp, uint32 A, uint16 V); +} diff --git a/waterbox/vb/vip.cpp b/waterbox/vb/vip.cpp index 9389e95ba4..4c31a29b2a 100644 --- a/waterbox/vb/vip.cpp +++ b/waterbox/vb/vip.cpp @@ -1,1395 +1,1392 @@ -/******************************************************************************/ -/* Mednafen Virtual Boy Emulation Module */ -/******************************************************************************/ -/* vip.cpp: -** Copyright (C) 2010-2016 Mednafen Team -** -** This program is free software; you can redistribute it and/or -** modify it under the terms of the GNU General Public License -** as published by the Free Software Foundation; either version 2 -** of the License, or (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software Foundation, Inc., -** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -#include "vb.h" -#include - -#define VIP_DBGMSG(format, ...) \ - { \ - } -//#define VIP_DBGMSG(format, ...) printf(format "\n", ## __VA_ARGS__) - -namespace MDFN_IEN_VB -{ - -static uint8 FB[2][2][0x6000]; -static uint16 CHR_RAM[0x8000 / sizeof(uint16)]; -static uint16 DRAM[0x20000 / sizeof(uint16)]; - -#define INT_SCAN_ERR 0x0001 -#define INT_LFB_END 0x0002 -#define INT_RFB_END 0x0004 -#define INT_GAME_START 0x0008 -#define INT_FRAME_START 0x0010 - -#define INT_SB_HIT 0x2000 -#define INT_XP_END 0x4000 -#define INT_TIME_ERR 0x8000 - -static uint16 InterruptPending; -static uint16 InterruptEnable; - -static uint8 BRTA, BRTB, BRTC, REST; -static uint8 Repeat; - -static void CopyFBColumnToTarget_Anaglyph(void) NO_INLINE; -static void CopyFBColumnToTarget_AnaglyphSlow(void) NO_INLINE; -static void CopyFBColumnToTarget_CScope(void) NO_INLINE; -static void CopyFBColumnToTarget_SideBySide(void) NO_INLINE; -static void CopyFBColumnToTarget_VLI(void) NO_INLINE; -static void CopyFBColumnToTarget_HLI(void) NO_INLINE; -static void (*CopyFBColumnToTarget)(void) = NULL; -static float VBLEDOnScale; -static uint32 VB3DMode; -static uint32 VB3DReverse; -static uint32 VBPrescale; -static uint32 VBSBS_Separation; -static uint32 HLILUT[256]; -static uint32 ColorLUT[2][256]; -static int32 BrightnessCache[4]; -static uint32 BrightCLUT[2][4]; - -static float ColorLUTNoGC[2][256][3]; -static uint32 AnaSlowColorLUT[256][256]; - -static bool VidSettingsDirty; -static bool ParallaxDisabled; -static uint32 Anaglyph_Colors[2]; -static uint32 Default_Color; - -static void MakeColorLUT() -{ - for (int lr = 0; lr < 2; lr++) - { - for (int i = 0; i < 256; i++) - { - float r, g, b; - uint32 modcolor_prime; - - if (VB3DMode == VB3DMODE_ANAGLYPH) - modcolor_prime = Anaglyph_Colors[lr ^ VB3DReverse]; - else - modcolor_prime = Default_Color; - - r = g = b = std::min(1.0, i * VBLEDOnScale / 255.0); - - // Modulate. - r = r * pow(((modcolor_prime >> 16) & 0xFF) / 255.0, 2.2 / 1.0); - g = g * pow(((modcolor_prime >> 8) & 0xFF) / 255.0, 2.2 / 1.0); - b = b * pow(((modcolor_prime >> 0) & 0xFF) / 255.0, 2.2 / 1.0); - - ColorLUTNoGC[lr][i][0] = r; - ColorLUTNoGC[lr][i][1] = g; - ColorLUTNoGC[lr][i][2] = b; - - // Apply gamma correction - const float r_prime = pow(r, 1.0 / 2.2); - const float g_prime = pow(g, 1.0 / 2.2); - const float b_prime = pow(b, 1.0 / 2.2); - - ColorLUT[lr][i] = (int)(b_prime * 255) & 0xff | (int)(g_prime * 255) << 8 & 0xff00 | (int)(r_prime * 255) << 16 & 0xff0000 | 0xff000000; - } - } - - // Anaglyph slow-mode LUT calculation - for (int l_b = 0; l_b < 256; l_b++) - { - for (int r_b = 0; r_b < 256; r_b++) - { - float r, g, b; - float r_prime, g_prime, b_prime; - - r = ColorLUTNoGC[0][l_b][0] + ColorLUTNoGC[1][r_b][0]; - g = ColorLUTNoGC[0][l_b][1] + ColorLUTNoGC[1][r_b][1]; - b = ColorLUTNoGC[0][l_b][2] + ColorLUTNoGC[1][r_b][2]; - - if (r > 1.0) - r = 1.0; - if (g > 1.0) - g = 1.0; - if (b > 1.0) - b = 1.0; - - r_prime = pow(r, 1.0 / 2.2); - g_prime = pow(g, 1.0 / 2.2); - b_prime = pow(b, 1.0 / 2.2); - - AnaSlowColorLUT[l_b][r_b] = (int)(b_prime * 255) & 0xff | (int)(g_prime * 255) << 8 & 0xff00 | (int)(r_prime * 255) << 16 & 0xff0000 | 0xff000000; - } - } -} - -static void RecalcBrightnessCache(void) -{ - static const int32 MaxTime = 255; - int32 CumulativeTime = (BRTA + 1 + BRTB + 1 + BRTC + 1 + REST + 1) + 1; - - //printf("BRTA: %d, BRTB: %d, BRTC: %d, Rest: %d --- %d\n", BRTA, BRTB, BRTC, REST, BRTA + 1 + BRTB + 1 + BRTC); - - BrightnessCache[0] = 0; - BrightnessCache[1] = 0; - BrightnessCache[2] = 0; - BrightnessCache[3] = 0; - - for (int i = 0; i < Repeat + 1; i++) - { - int32 btemp[4]; - - if ((i * CumulativeTime) >= MaxTime) - break; - - btemp[1] = (i * CumulativeTime) + BRTA; - if (btemp[1] > MaxTime) - btemp[1] = MaxTime; - btemp[1] -= (i * CumulativeTime); - if (btemp[1] < 0) - btemp[1] = 0; - - btemp[2] = (i * CumulativeTime) + BRTA + 1 + BRTB; - if (btemp[2] > MaxTime) - btemp[2] = MaxTime; - btemp[2] -= (i * CumulativeTime) + BRTA + 1; - if (btemp[2] < 0) - btemp[2] = 0; - - //btemp[3] = (i * CumulativeTime) + BRTA + 1 + BRTB + 1 + BRTC; - //if(btemp[3] > MaxTime) - // btemp[3] = MaxTime; - //btemp[3] -= (i * CumulativeTime); - //if(btemp[3] < 0) - // btemp[3] = 0; - - btemp[3] = (i * CumulativeTime) + BRTA + BRTB + BRTC + 1; - if (btemp[3] > MaxTime) - btemp[3] = MaxTime; - btemp[3] -= (i * CumulativeTime) + 1; - if (btemp[3] < 0) - btemp[3] = 0; - - BrightnessCache[1] += btemp[1]; - BrightnessCache[2] += btemp[2]; - BrightnessCache[3] += btemp[3]; - } - - //printf("BC: %d %d %d %d\n", BrightnessCache[0], BrightnessCache[1], BrightnessCache[2], BrightnessCache[3]); - - for (int lr = 0; lr < 2; lr++) - for (int i = 0; i < 4; i++) - { - BrightCLUT[lr][i] = ColorLUT[lr][BrightnessCache[i]]; - //printf("%d %d, %08x\n", lr, i, BrightCLUT[lr][i]); - } -} - -static void Recalc3DModeStuff(bool non_rgb_output = false) -{ - switch (VB3DMode) - { - default: - if (((Anaglyph_Colors[0] & 0xFF) && (Anaglyph_Colors[1] & 0xFF)) || - ((Anaglyph_Colors[0] & 0xFF00) && (Anaglyph_Colors[1] & 0xFF00)) || - ((Anaglyph_Colors[0] & 0xFF0000) && (Anaglyph_Colors[1] & 0xFF0000)) || - non_rgb_output) - { - CopyFBColumnToTarget = CopyFBColumnToTarget_AnaglyphSlow; - } - else - CopyFBColumnToTarget = CopyFBColumnToTarget_Anaglyph; - break; - - case VB3DMODE_CSCOPE: - CopyFBColumnToTarget = CopyFBColumnToTarget_CScope; - break; - - case VB3DMODE_SIDEBYSIDE: - CopyFBColumnToTarget = CopyFBColumnToTarget_SideBySide; - break; - - case VB3DMODE_VLI: - CopyFBColumnToTarget = CopyFBColumnToTarget_VLI; - break; - - case VB3DMODE_HLI: - CopyFBColumnToTarget = CopyFBColumnToTarget_HLI; - break; - } - RecalcBrightnessCache(); -} - -void VIP_Set3DMode(uint32 mode, bool reverse, uint32 prescale, uint32 sbs_separation) -{ - VB3DMode = mode; - VB3DReverse = reverse ? 1 : 0; - VBPrescale = prescale; - VBSBS_Separation = sbs_separation; - - VidSettingsDirty = true; - - for (uint32 p = 0; p < 256; p++) - { - uint32 v; - uint8 s[4]; - - s[0] = (p >> 0) & 0x3; - s[1] = (p >> 2) & 0x3; - s[2] = (p >> 4) & 0x3; - s[3] = (p >> 6) & 0x3; - - v = 0; - for (unsigned int i = 0, shifty = 0; i < 4; i++) - { - for (unsigned int ps = 0; ps < prescale; ps++) - { - v |= s[i] << shifty; - shifty += 2; - } - } - - HLILUT[p] = v; - } -} - -void VIP_SetParallaxDisable(bool disabled) -{ - ParallaxDisabled = disabled; -} - -void VIP_SetDefaultColor(uint32 default_color) -{ - Default_Color = default_color; - - VidSettingsDirty = true; -} - -void VIP_SetLEDOnScale(float coeff) -{ - VBLEDOnScale = coeff; -} - -void VIP_SetAnaglyphColors(uint32 lcolor, uint32 rcolor) -{ - Anaglyph_Colors[0] = lcolor; - Anaglyph_Colors[1] = rcolor; - - VidSettingsDirty = true; -} - -static uint16 FRMCYC; - -static uint16 DPCTRL; -static bool DisplayActive; - -#define XPCTRL_XP_RST 0x0001 -#define XPCTRL_XP_EN 0x0002 -static uint16 XPCTRL; -static uint16 SBCMP; // Derived from XPCTRL - -static uint16 SPT[4]; // SPT0~SPT3, 5f848~5f84e -static uint16 GPLT[4]; -static uint8 GPLT_Cache[4][4]; - -static INLINE void Recalc_GPLT_Cache(int which) -{ - for (int i = 0; i < 4; i++) - GPLT_Cache[which][i] = (GPLT[which] >> (i * 2)) & 3; -} - -static uint16 JPLT[4]; -static uint8 JPLT_Cache[4][4]; - -static INLINE void Recalc_JPLT_Cache(int which) -{ - for (int i = 0; i < 4; i++) - JPLT_Cache[which][i] = (JPLT[which] >> (i * 2)) & 3; -} - -static uint16 BKCOL; - -// -// -// -static int32 CalcNextEvent(void); - -static int32 last_ts; - -static uint32 Column; -static int32 ColumnCounter; - -static int32 DisplayRegion; -static bool DisplayFB; - -static int32 GameFrameCounter; - -static int32 DrawingCounter; -static bool DrawingActive; -static bool DrawingFB; -static uint32 DrawingBlock; -static int32 SB_Latch; -static int32 SBOUT_InactiveTime; - -//static uint8 CTA_L, CTA_R; - -static void CheckIRQ(void) -{ - VBIRQ_Assert(VBIRQ_SOURCE_VIP, (bool)(InterruptEnable & InterruptPending)); - -#if 0 - printf("%08x\n", InterruptEnable & InterruptPending); - if((bool)(InterruptEnable & InterruptPending)) - puts("IRQ asserted"); - else - puts("IRQ not asserted"); -#endif -} - -void VIP_Init(void) -{ - ParallaxDisabled = false; - Anaglyph_Colors[0] = 0xFF0000; - Anaglyph_Colors[1] = 0x0000FF; - VB3DMode = VB3DMODE_ANAGLYPH; - Default_Color = 0xFFFFFF; - VB3DReverse = 0; - VBPrescale = 1; - VBSBS_Separation = 0; - - VidSettingsDirty = true; -} - -void VIP_Power(void) -{ - Repeat = 0; - SB_Latch = 0; - SBOUT_InactiveTime = -1; - last_ts = 0; - - Column = 0; - ColumnCounter = 259; - - DisplayRegion = 0; - DisplayFB = 0; - - GameFrameCounter = 0; - - DrawingCounter = 0; - DrawingActive = false; - DrawingFB = 0; - DrawingBlock = 0; - - DPCTRL = 2; - DisplayActive = false; - - memset(FB, 0, 0x6000 * 2 * 2); - memset(CHR_RAM, 0, 0x8000); - memset(DRAM, 0, 0x20000); - - InterruptPending = 0; - InterruptEnable = 0; - - BRTA = 0; - BRTB = 0; - BRTC = 0; - REST = 0; - - FRMCYC = 0; - - XPCTRL = 0; - SBCMP = 0; - - for (int i = 0; i < 4; i++) - { - SPT[i] = 0; - GPLT[i] = 0; - JPLT[i] = 0; - - Recalc_GPLT_Cache(i); - Recalc_JPLT_Cache(i); - } - - BKCOL = 0; -} - -static INLINE uint16 ReadRegister(int32 ×tamp, uint32 A) -{ - uint16 ret = 0; //0xFFFF; - - if (A & 1) - VIP_DBGMSG("Misaligned VIP Read: %08x", A); - - switch (A & 0xFE) - { - default: - VIP_DBGMSG("Unknown VIP register read: %08x", A); - break; - - case 0x00: - ret = InterruptPending; - break; - - case 0x02: - ret = InterruptEnable; - break; - - case 0x20: //printf("Read DPSTTS at %d\n", timestamp); - ret = DPCTRL & 0x702; - if ((DisplayRegion & 1) && DisplayActive) - { - unsigned int DPBSY = 1 << ((DisplayRegion >> 1) & 1); - - if (DisplayFB) - DPBSY <<= 2; - - ret |= DPBSY << 2; - } - //if(!(DisplayRegion & 1)) // FIXME? (Had to do it this way for Galactic Pinball...) - ret |= 1 << 6; - break; - - // Note: Upper bits of BRTA, BRTB, BRTC, and REST(?) are 0 when read(on real hardware) - case 0x24: - ret = BRTA; - break; - - case 0x26: - ret = BRTB; - break; - - case 0x28: - ret = BRTC; - break; - - case 0x2A: - ret = REST; - break; - - case 0x30: - ret = 0xFFFF; - break; - - case 0x40: - ret = XPCTRL & 0x2; - if (DrawingActive) - { - ret |= (1 + DrawingFB) << 2; - } - if (timestamp < SBOUT_InactiveTime) - { - ret |= 0x8000; - ret |= /*DrawingBlock*/ SB_Latch << 8; - } - break; // XPSTTS, read-only - - case 0x44: - ret = 2; // VIP version. 2 is a known valid version, while the validity of other numbers is unknown, so we'll just go with 2. - break; - - case 0x48: - case 0x4a: - case 0x4c: - case 0x4e: - ret = SPT[(A >> 1) & 3]; - break; - - case 0x60: - case 0x62: - case 0x64: - case 0x66: - ret = GPLT[(A >> 1) & 3]; - break; - - case 0x68: - case 0x6a: - case 0x6c: - case 0x6e: - ret = JPLT[(A >> 1) & 3]; - break; - - case 0x70: - ret = BKCOL; - break; - } - - return (ret); -} - -static INLINE void WriteRegister(int32 ×tamp, uint32 A, uint16 V) -{ - if (A & 1) - VIP_DBGMSG("Misaligned VIP Write: %08x %04x", A, V); - - switch (A & 0xFE) - { - default: - VIP_DBGMSG("Unknown VIP register write: %08x %04x", A, V); - break; - - case 0x00: - break; // Interrupt pending, read-only - - case 0x02: - { - InterruptEnable = V & 0xE01F; - - VIP_DBGMSG("Interrupt Enable: %04x", V); - - if (V & 0x2000) - VIP_DBGMSG("Warning: VIP SB Hit Interrupt enable: %04x\n", V); - CheckIRQ(); - } - break; - - case 0x04: - InterruptPending &= ~V; - CheckIRQ(); - break; - - case 0x20: - break; // Display control, read-only. - - case 0x22: - DPCTRL = V & (0x703); // Display-control, write-only - if (V & 1) - { - DisplayActive = false; - InterruptPending &= ~(INT_TIME_ERR | INT_FRAME_START | INT_GAME_START | INT_RFB_END | INT_LFB_END | INT_SCAN_ERR); - CheckIRQ(); - } - break; - - case 0x24: - BRTA = V & 0xFF; // BRTA - RecalcBrightnessCache(); - break; - - case 0x26: - BRTB = V & 0xFF; // BRTB - RecalcBrightnessCache(); - break; - - case 0x28: - BRTC = V & 0xFF; // BRTC - RecalcBrightnessCache(); - break; - - case 0x2A: - REST = V & 0xFF; // REST - RecalcBrightnessCache(); - break; - - case 0x2E: - FRMCYC = V & 0xF; // FRMCYC, write-only? - break; - - case 0x30: - break; // CTA, read-only( - - case 0x40: - break; // XPSTTS, read-only - - case 0x42: - XPCTRL = V & 0x0002; // XPCTRL, write-only - SBCMP = (V >> 8) & 0x1F; - - if (V & 1) - { - VIP_DBGMSG("XPRST"); - DrawingActive = 0; - DrawingCounter = 0; - InterruptPending &= ~(INT_SB_HIT | INT_XP_END | INT_TIME_ERR); - CheckIRQ(); - } - break; - - case 0x44: - break; // Version Control, read-only? - - case 0x48: - case 0x4a: - case 0x4c: - case 0x4e: - SPT[(A >> 1) & 3] = V & 0x3FF; - break; - - case 0x60: - case 0x62: - case 0x64: - case 0x66: - GPLT[(A >> 1) & 3] = V & 0xFC; - Recalc_GPLT_Cache((A >> 1) & 3); - break; - - case 0x68: - case 0x6a: - case 0x6c: - case 0x6e: - JPLT[(A >> 1) & 3] = V & 0xFC; - Recalc_JPLT_Cache((A >> 1) & 3); - break; - - case 0x70: - BKCOL = V & 0x3; - break; - } -} - -// -// Don't update the VIP state on reads/writes, the event system will update it with enough precision as far as VB software cares. -// - -MDFN_FASTCALL uint8 VIP_Read8(int32 ×tamp, uint32 A) -{ - uint8 ret = 0; //0xFF; - - //VIP_Update(timestamp); - - switch (A >> 16) - { - case 0x0: - case 0x1: - if ((A & 0x7FFF) >= 0x6000) - { - ret = ne16_rbo_le(CHR_RAM, (A & 0x1FFF) | ((A >> 2) & 0x6000)); - } - else - { - ret = FB[(A >> 15) & 1][(A >> 16) & 1][A & 0x7FFF]; - } - break; - - case 0x2: - case 0x3: - ret = ne16_rbo_le(DRAM, A & 0x1FFFF); - break; - - case 0x4: - case 0x5: - if (A >= 0x5E000) - ret = ReadRegister(timestamp, A); - else - VIP_DBGMSG("Unknown VIP Read: %08x", A); - break; - - case 0x6: - break; - - case 0x7: - if (A >= 0x8000) - { - ret = ne16_rbo_le(CHR_RAM, A & 0x7FFF); - } - else - VIP_DBGMSG("Unknown VIP Read: %08x", A); - break; - - default: - VIP_DBGMSG("Unknown VIP Read: %08x", A); - break; - } - - //VB_SetEvent(VB_EVENT_VIP, timestamp + CalcNextEvent()); - - return (ret); -} - -MDFN_FASTCALL uint16 VIP_Read16(int32 ×tamp, uint32 A) -{ - uint16 ret = 0; //0xFFFF; - - //VIP_Update(timestamp); - - switch (A >> 16) - { - case 0x0: - case 0x1: - if ((A & 0x7FFF) >= 0x6000) - { - ret = ne16_rbo_le(CHR_RAM, (A & 0x1FFF) | ((A >> 2) & 0x6000)); - } - else - { - ret = MDFN_de16lsb(&FB[(A >> 15) & 1][(A >> 16) & 1][A & 0x7FFF]); - } - break; - - case 0x2: - case 0x3: - ret = ne16_rbo_le(DRAM, A & 0x1FFFF); - break; - - case 0x4: - case 0x5: - if (A >= 0x5E000) - ret = ReadRegister(timestamp, A); - else - VIP_DBGMSG("Unknown VIP Read: %08x", A); - break; - - case 0x6: - break; - - case 0x7: - if (A >= 0x8000) - { - ret = ne16_rbo_le(CHR_RAM, A & 0x7FFF); - } - else - VIP_DBGMSG("Unknown VIP Read: %08x", A); - break; - - default: - VIP_DBGMSG("Unknown VIP Read: %08x", A); - break; - } - - //VB_SetEvent(VB_EVENT_VIP, timestamp + CalcNextEvent()); - return (ret); -} - -MDFN_FASTCALL void VIP_Write8(int32 ×tamp, uint32 A, uint8 V) -{ - //VIP_Update(timestamp); - - //if(A >= 0x3DC00 && A < 0x3E000) - // printf("%08x %02x\n", A, V); - - switch (A >> 16) - { - case 0x0: - case 0x1: - if ((A & 0x7FFF) >= 0x6000) - ne16_wbo_le(CHR_RAM, (A & 0x1FFF) | ((A >> 2) & 0x6000), V); - else - FB[(A >> 15) & 1][(A >> 16) & 1][A & 0x7FFF] = V; - break; - - case 0x2: - case 0x3: - ne16_wbo_le(DRAM, A & 0x1FFFF, V); - break; - - case 0x4: - case 0x5: - if (A >= 0x5E000) - WriteRegister(timestamp, A, V); - else - VIP_DBGMSG("Unknown VIP Write: %08x %02x", A, V); - break; - - case 0x6: - VIP_DBGMSG("Unknown VIP Write: %08x %02x", A, V); - break; - - case 0x7: - if (A >= 0x8000) - ne16_wbo_le(CHR_RAM, A & 0x7FFF, V); - else - VIP_DBGMSG("Unknown VIP Write: %08x %02x", A, V); - break; - - default: - VIP_DBGMSG("Unknown VIP Write: %08x %02x", A, V); - break; - } - - //VB_SetEvent(VB_EVENT_VIP, timestamp + CalcNextEvent()); -} - -MDFN_FASTCALL void VIP_Write16(int32 ×tamp, uint32 A, uint16 V) -{ - //VIP_Update(timestamp); - - //if(A >= 0x3DC00 && A < 0x3E000) - // printf("%08x %04x\n", A, V); - - switch (A >> 16) - { - case 0x0: - case 0x1: - if ((A & 0x7FFF) >= 0x6000) - ne16_wbo_le(CHR_RAM, (A & 0x1FFF) | ((A >> 2) & 0x6000), V); - else - MDFN_en16lsb(&FB[(A >> 15) & 1][(A >> 16) & 1][A & 0x7FFF], V); - break; - - case 0x2: - case 0x3: - ne16_wbo_le(DRAM, A & 0x1FFFF, V); - break; - - case 0x4: - case 0x5: - if (A >= 0x5E000) - WriteRegister(timestamp, A, V); - else - VIP_DBGMSG("Unknown VIP Write: %08x %04x", A, V); - break; - - case 0x6: - VIP_DBGMSG("Unknown VIP Write: %08x %04x", A, V); - break; - - case 0x7: - if (A >= 0x8000) - ne16_wbo_le(CHR_RAM, A & 0x7FFF, V); - else - VIP_DBGMSG("Unknown VIP Write: %08x %04x", A, V); - break; - - default: - VIP_DBGMSG("Unknown VIP Write: %08x %04x", A, V); - break; - } - - //VB_SetEvent(VB_EVENT_VIP, timestamp + CalcNextEvent()); -} - -static MDFN_Surface real_surface; -static MDFN_Surface *surface; - -void VIP_StartFrame(EmulateSpecStruct *espec) -{ - // puts("Start frame"); - - if (VidSettingsDirty) - { - MakeColorLUT(); - Recalc3DModeStuff(); - - VidSettingsDirty = false; - } - - espec->DisplayRect.x = 0; - espec->DisplayRect.y = 0; - - switch (VB3DMode) - { - default: - espec->DisplayRect.w = 384; - espec->DisplayRect.h = 224; - break; - - case VB3DMODE_VLI: - espec->DisplayRect.w = 768 * VBPrescale; - espec->DisplayRect.h = 224; - break; - - case VB3DMODE_HLI: - espec->DisplayRect.w = 384; - espec->DisplayRect.h = 448 * VBPrescale; - break; - - case VB3DMODE_CSCOPE: - espec->DisplayRect.w = 512; - espec->DisplayRect.h = 384; - break; - - case VB3DMODE_SIDEBYSIDE: - espec->DisplayRect.w = 768 + VBSBS_Separation; - espec->DisplayRect.h = 224; - break; - } - - surface = &real_surface; - real_surface.pixels = espec->pixels; - real_surface.pitch32 = espec->DisplayRect.w; -} - -void VIP_ResetTS(void) -{ - if (SBOUT_InactiveTime >= 0) - SBOUT_InactiveTime -= last_ts; - last_ts = 0; -} - -static int32 CalcNextEvent(void) -{ - return (ColumnCounter); -} - -#include "vip_draw.inc" - -static INLINE void CopyFBColumnToTarget_Anaglyph_BASE(const bool DisplayActive_arg, const int lr) -{ - const int fb = DisplayFB; - uint32 *target = surface->pixels + Column; - const int32 pitch32 = surface->pitch32; - const uint8 *fb_source = &FB[fb][lr][64 * Column]; - - for (int y = 56; y; y--) - { - uint32 source_bits = *fb_source; - - for (int y_sub = 4; y_sub; y_sub--) - { - uint32 pixel = BrightCLUT[lr][source_bits & 3]; - - if (!DisplayActive_arg) - pixel = 0; - - if (lr) - *target |= pixel; - else - *target = pixel; - - source_bits >>= 2; - target += pitch32; - } - fb_source++; - } -} - -static void CopyFBColumnToTarget_Anaglyph(void) -{ - const int lr = (DisplayRegion & 2) >> 1; - - if (!DisplayActive) - { - if (!lr) - CopyFBColumnToTarget_Anaglyph_BASE(0, 0); - else - CopyFBColumnToTarget_Anaglyph_BASE(0, 1); - } - else - { - if (!lr) - CopyFBColumnToTarget_Anaglyph_BASE(1, 0); - else - CopyFBColumnToTarget_Anaglyph_BASE(1, 1); - } -} - -static uint32 AnaSlowBuf[384][224]; - -static INLINE void CopyFBColumnToTarget_AnaglyphSlow_BASE(const bool DisplayActive_arg, const int lr) -{ - const int fb = DisplayFB; - const uint8 *fb_source = &FB[fb][lr][64 * Column]; - - if (!lr) - { - uint32 *target = AnaSlowBuf[Column]; - - for (int y = 56; y; y--) - { - uint32 source_bits = *fb_source; - - for (int y_sub = 4; y_sub; y_sub--) - { - uint32 pixel = BrightnessCache[source_bits & 3]; - - if (!DisplayActive_arg) - pixel = 0; - - *target = pixel; - source_bits >>= 2; - target++; - } - fb_source++; - } - } - else - { - uint32 *target = surface->pixels + Column; - const uint32 *left_src = AnaSlowBuf[Column]; - const int32 pitch32 = surface->pitch32; - - for (int y = 56; y; y--) - { - uint32 source_bits = *fb_source; - - for (int y_sub = 4; y_sub; y_sub--) - { - uint32 pixel = AnaSlowColorLUT[*left_src][DisplayActive_arg ? BrightnessCache[source_bits & 3] : 0]; - - *target = pixel; - - source_bits >>= 2; - target += pitch32; - left_src++; - } - fb_source++; - } - } -} - -static void CopyFBColumnToTarget_AnaglyphSlow(void) -{ - const int lr = (DisplayRegion & 2) >> 1; - - if (!DisplayActive) - { - if (!lr) - CopyFBColumnToTarget_AnaglyphSlow_BASE(0, 0); - else - CopyFBColumnToTarget_AnaglyphSlow_BASE(0, 1); - } - else - { - if (!lr) - CopyFBColumnToTarget_AnaglyphSlow_BASE(1, 0); - else - CopyFBColumnToTarget_AnaglyphSlow_BASE(1, 1); - } -} - -static void CopyFBColumnToTarget_CScope_BASE(const bool DisplayActive_arg, const int lr, const int dest_lr) -{ - const int fb = DisplayFB; - uint32 *target = surface->pixels + (dest_lr ? 512 - 16 - 1 : 16) + (dest_lr ? Column : 383 - Column) * surface->pitch32; - const uint8 *fb_source = &FB[fb][lr][64 * Column]; - - for (int y = 56; y; y--) - { - uint32 source_bits = *fb_source; - - for (int y_sub = 4; y_sub; y_sub--) - { - if (DisplayActive_arg) - *target = BrightCLUT[lr][source_bits & 3]; - else - *target = 0; - - source_bits >>= 2; - if (dest_lr) - target--; - else - target++; - } - fb_source++; - } -} - -static void CopyFBColumnToTarget_CScope(void) -{ - const int lr = (DisplayRegion & 2) >> 1; - - if (!DisplayActive) - { - if (!lr) - CopyFBColumnToTarget_CScope_BASE(0, 0, 0 ^ VB3DReverse); - else - CopyFBColumnToTarget_CScope_BASE(0, 1, 1 ^ VB3DReverse); - } - else - { - if (!lr) - CopyFBColumnToTarget_CScope_BASE(1, 0, 0 ^ VB3DReverse); - else - CopyFBColumnToTarget_CScope_BASE(1, 1, 1 ^ VB3DReverse); - } -} - -static void CopyFBColumnToTarget_SideBySide_BASE(const bool DisplayActive_arg, const int lr, const int dest_lr) -{ - const int fb = DisplayFB; - uint32 *target = surface->pixels + Column + (dest_lr ? (384 + VBSBS_Separation) : 0); - const int32 pitch32 = surface->pitch32; - const uint8 *fb_source = &FB[fb][lr][64 * Column]; - - for (int y = 56; y; y--) - { - uint32 source_bits = *fb_source; - - for (int y_sub = 4; y_sub; y_sub--) - { - if (DisplayActive_arg) - *target = BrightCLUT[lr][source_bits & 3]; - else - *target = 0; - source_bits >>= 2; - target += pitch32; - } - fb_source++; - } -} - -static void CopyFBColumnToTarget_SideBySide(void) -{ - const int lr = (DisplayRegion & 2) >> 1; - - if (!DisplayActive) - { - if (!lr) - CopyFBColumnToTarget_SideBySide_BASE(0, 0, 0 ^ VB3DReverse); - else - CopyFBColumnToTarget_SideBySide_BASE(0, 1, 1 ^ VB3DReverse); - } - else - { - if (!lr) - CopyFBColumnToTarget_SideBySide_BASE(1, 0, 0 ^ VB3DReverse); - else - CopyFBColumnToTarget_SideBySide_BASE(1, 1, 1 ^ VB3DReverse); - } -} - -static INLINE void CopyFBColumnToTarget_VLI_BASE(const bool DisplayActive_arg, const int lr, const int dest_lr) -{ - const int fb = DisplayFB; - uint32 *target = surface->pixels + Column * 2 * VBPrescale + dest_lr; - const int32 pitch32 = surface->pitch32; - const uint8 *fb_source = &FB[fb][lr][64 * Column]; - - for (int y = 56; y; y--) - { - uint32 source_bits = *fb_source; - - for (int y_sub = 4; y_sub; y_sub--) - { - uint32 tv; - - if (DisplayActive_arg) - tv = BrightCLUT[lr][source_bits & 3]; - else - tv = 0; - - for (uint32 ps = 0; ps < VBPrescale; ps++) - target[ps * 2] = tv; - - source_bits >>= 2; - target += pitch32; - } - fb_source++; - } -} - -static void CopyFBColumnToTarget_VLI(void) -{ - const int lr = (DisplayRegion & 2) >> 1; - - if (!DisplayActive) - { - if (!lr) - CopyFBColumnToTarget_VLI_BASE(0, 0, 0 ^ VB3DReverse); - else - CopyFBColumnToTarget_VLI_BASE(0, 1, 1 ^ VB3DReverse); - } - else - { - if (!lr) - CopyFBColumnToTarget_VLI_BASE(1, 0, 0 ^ VB3DReverse); - else - CopyFBColumnToTarget_VLI_BASE(1, 1, 1 ^ VB3DReverse); - } -} - -static INLINE void CopyFBColumnToTarget_HLI_BASE(const bool DisplayActive_arg, const int lr, const int dest_lr) -{ - const int fb = DisplayFB; - const int32 pitch32 = surface->pitch32; - uint32 *target = surface->pixels + Column + dest_lr * pitch32; - const uint8 *fb_source = &FB[fb][lr][64 * Column]; - - if (VBPrescale <= 4) - for (int y = 56; y; y--) - { - uint32 source_bits = HLILUT[*fb_source]; - - for (int y_sub = 4 * VBPrescale; y_sub; y_sub--) - { - if (DisplayActive_arg) - *target = BrightCLUT[lr][source_bits & 3]; - else - *target = 0; - - target += pitch32 * 2; - source_bits >>= 2; - } - fb_source++; - } - else - for (int y = 56; y; y--) - { - uint32 source_bits = *fb_source; - - for (int y_sub = 4; y_sub; y_sub--) - { - for (uint32 ps = 0; ps < VBPrescale; ps++) - { - if (DisplayActive_arg) - *target = BrightCLUT[lr][source_bits & 3]; - else - *target = 0; - - target += pitch32 * 2; - } - - source_bits >>= 2; - } - fb_source++; - } -} - -static void CopyFBColumnToTarget_HLI(void) -{ - const int lr = (DisplayRegion & 2) >> 1; - - if (!DisplayActive) - { - if (!lr) - CopyFBColumnToTarget_HLI_BASE(0, 0, 0 ^ VB3DReverse); - else - CopyFBColumnToTarget_HLI_BASE(0, 1, 1 ^ VB3DReverse); - } - else - { - if (!lr) - CopyFBColumnToTarget_HLI_BASE(1, 0, 0 ^ VB3DReverse); - else - CopyFBColumnToTarget_HLI_BASE(1, 1, 1 ^ VB3DReverse); - } -} - -v810_timestamp_t MDFN_FASTCALL VIP_Update(const v810_timestamp_t timestamp) -{ - int32 clocks = timestamp - last_ts; - int32 running_timestamp = timestamp; - - while (clocks > 0) - { - int32 chunk_clocks = clocks; - - if (DrawingCounter > 0 && chunk_clocks > DrawingCounter) - chunk_clocks = DrawingCounter; - if (chunk_clocks > ColumnCounter) - chunk_clocks = ColumnCounter; - - running_timestamp += chunk_clocks; - - if (DrawingCounter > 0) - { - DrawingCounter -= chunk_clocks; - if (DrawingCounter <= 0) - { - alignas(8) uint8 DrawingBuffers[2][512 * 8]; // Don't decrease this from 512 unless you adjust vip_draw.inc(including areas that draw off-visible >= 384 and >= -7 for speed reasons) - - VIP_DrawBlock(DrawingBlock, DrawingBuffers[0] + 8, DrawingBuffers[1] + 8); - - for (int lr = 0; lr < 2; lr++) - { - uint8 *FB_Target = FB[DrawingFB][lr] + DrawingBlock * 2; - - for (int x = 0; x < 384; x++) - { - FB_Target[64 * x + 0] = (DrawingBuffers[lr][8 + x + 512 * 0] << 0) | (DrawingBuffers[lr][8 + x + 512 * 1] << 2) | (DrawingBuffers[lr][8 + x + 512 * 2] << 4) | (DrawingBuffers[lr][8 + x + 512 * 3] << 6); - FB_Target[64 * x + 1] = (DrawingBuffers[lr][8 + x + 512 * 4] << 0) | (DrawingBuffers[lr][8 + x + 512 * 5] << 2) | (DrawingBuffers[lr][8 + x + 512 * 6] << 4) | (DrawingBuffers[lr][8 + x + 512 * 7] << 6); - } - } - - SBOUT_InactiveTime = running_timestamp + 1120; - SB_Latch = DrawingBlock; // Not exactly correct, but probably doesn't matter. - - DrawingBlock++; - if (DrawingBlock == 28) - { - DrawingActive = false; - - InterruptPending |= INT_XP_END; - CheckIRQ(); - } - else - DrawingCounter += 1120 * 4; - } - } - - ColumnCounter -= chunk_clocks; - if (ColumnCounter == 0) - { - if (DisplayRegion & 1) - { - if (!(Column & 3)) - { - const int lr = (DisplayRegion & 2) >> 1; - uint16 ctdata = ne16_rbo_le(DRAM, 0x1DFFE - ((Column >> 2) * 2) - (lr ? 0 : 0x200)); - - //printf("%02x, repeat: %02x\n", ctdata & 0xFF, ctdata >> 8); - - if ((ctdata >> 8) != Repeat) - { - Repeat = ctdata >> 8; - RecalcBrightnessCache(); - } - } - CopyFBColumnToTarget(); - } - - ColumnCounter = 259; - Column++; - if (Column == 384) - { - Column = 0; - - if (DisplayActive) - { - if (DisplayRegion & 1) // Did we just finish displaying an active region? - { - if (DisplayRegion & 2) // finished displaying right eye - InterruptPending |= INT_RFB_END; - else // Otherwise, left eye - InterruptPending |= INT_LFB_END; - - CheckIRQ(); - } - } - - DisplayRegion = (DisplayRegion + 1) & 3; - - if (DisplayRegion == 0) // New frame start - { - DisplayActive = DPCTRL & 0x2; - - if (DisplayActive) - { - InterruptPending |= INT_FRAME_START; - CheckIRQ(); - } - GameFrameCounter++; - if (GameFrameCounter > FRMCYC) // New game frame start? - { - InterruptPending |= INT_GAME_START; - CheckIRQ(); - - if (XPCTRL & XPCTRL_XP_EN) - { - DisplayFB ^= 1; - - DrawingBlock = 0; - DrawingActive = true; - DrawingCounter = 1120 * 4; - DrawingFB = DisplayFB ^ 1; - } - - GameFrameCounter = 0; - } - - VB_ExitLoop(); - } - } - } - - clocks -= chunk_clocks; - } - - last_ts = timestamp; - - return (timestamp + CalcNextEvent()); -} -} +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* vip.cpp: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "vb.h" +#include + +#define VIP_DBGMSG(format, ...) \ + { \ + } +//#define VIP_DBGMSG(format, ...) printf(format "\n", ## __VA_ARGS__) + +namespace MDFN_IEN_VB +{ + +static uint8 FB[2][2][0x6000]; +static uint16 CHR_RAM[0x8000 / sizeof(uint16)]; +static uint16 DRAM[0x20000 / sizeof(uint16)]; + +#define INT_SCAN_ERR 0x0001 +#define INT_LFB_END 0x0002 +#define INT_RFB_END 0x0004 +#define INT_GAME_START 0x0008 +#define INT_FRAME_START 0x0010 + +#define INT_SB_HIT 0x2000 +#define INT_XP_END 0x4000 +#define INT_TIME_ERR 0x8000 + +static uint16 InterruptPending; +static uint16 InterruptEnable; + +static uint8 BRTA, BRTB, BRTC, REST; +static uint8 Repeat; + +static void CopyFBColumnToTarget_Anaglyph(void) NO_INLINE; +static void CopyFBColumnToTarget_AnaglyphSlow(void) NO_INLINE; +static void CopyFBColumnToTarget_CScope(void) NO_INLINE; +static void CopyFBColumnToTarget_SideBySide(void) NO_INLINE; +static void CopyFBColumnToTarget_VLI(void) NO_INLINE; +static void CopyFBColumnToTarget_HLI(void) NO_INLINE; +static void (*CopyFBColumnToTarget)(void) = NULL; +static float VBLEDOnScale; +static uint32 VB3DMode; +static uint32 VB3DReverse; +static uint32 VBPrescale; +static uint32 VBSBS_Separation; +static uint32 HLILUT[256]; +static uint32 ColorLUT[2][256]; +static int32 BrightnessCache[4]; +static uint32 BrightCLUT[2][4]; + +static float ColorLUTNoGC[2][256][3]; +static uint32 AnaSlowColorLUT[256][256]; + +static bool VidSettingsDirty; +static bool ParallaxDisabled; +static uint32 Anaglyph_Colors[2]; +static uint32 Default_Color; + +static void MakeColorLUT() +{ + for (int lr = 0; lr < 2; lr++) + { + for (int i = 0; i < 256; i++) + { + float r, g, b; + uint32 modcolor_prime; + + if (VB3DMode == VB3DMODE_ANAGLYPH) + modcolor_prime = Anaglyph_Colors[lr ^ VB3DReverse]; + else + modcolor_prime = Default_Color; + + r = g = b = std::min(1.0, i * VBLEDOnScale / 255.0); + + // Modulate. + r = r * pow(((modcolor_prime >> 16) & 0xFF) / 255.0, 2.2 / 1.0); + g = g * pow(((modcolor_prime >> 8) & 0xFF) / 255.0, 2.2 / 1.0); + b = b * pow(((modcolor_prime >> 0) & 0xFF) / 255.0, 2.2 / 1.0); + + ColorLUTNoGC[lr][i][0] = r; + ColorLUTNoGC[lr][i][1] = g; + ColorLUTNoGC[lr][i][2] = b; + + // Apply gamma correction + const float r_prime = pow(r, 1.0 / 2.2); + const float g_prime = pow(g, 1.0 / 2.2); + const float b_prime = pow(b, 1.0 / 2.2); + + ColorLUT[lr][i] = (int)(b_prime * 255) & 0xff | (int)(g_prime * 255) << 8 & 0xff00 | (int)(r_prime * 255) << 16 & 0xff0000 | 0xff000000; + } + } + + // Anaglyph slow-mode LUT calculation + for (int l_b = 0; l_b < 256; l_b++) + { + for (int r_b = 0; r_b < 256; r_b++) + { + float r, g, b; + float r_prime, g_prime, b_prime; + + r = ColorLUTNoGC[0][l_b][0] + ColorLUTNoGC[1][r_b][0]; + g = ColorLUTNoGC[0][l_b][1] + ColorLUTNoGC[1][r_b][1]; + b = ColorLUTNoGC[0][l_b][2] + ColorLUTNoGC[1][r_b][2]; + + if (r > 1.0) + r = 1.0; + if (g > 1.0) + g = 1.0; + if (b > 1.0) + b = 1.0; + + r_prime = pow(r, 1.0 / 2.2); + g_prime = pow(g, 1.0 / 2.2); + b_prime = pow(b, 1.0 / 2.2); + + AnaSlowColorLUT[l_b][r_b] = (int)(b_prime * 255) & 0xff | (int)(g_prime * 255) << 8 & 0xff00 | (int)(r_prime * 255) << 16 & 0xff0000 | 0xff000000; + } + } +} + +static void RecalcBrightnessCache(void) +{ + static const int32 MaxTime = 255; + int32 CumulativeTime = (BRTA + 1 + BRTB + 1 + BRTC + 1 + REST + 1) + 1; + + //printf("BRTA: %d, BRTB: %d, BRTC: %d, Rest: %d --- %d\n", BRTA, BRTB, BRTC, REST, BRTA + 1 + BRTB + 1 + BRTC); + + BrightnessCache[0] = 0; + BrightnessCache[1] = 0; + BrightnessCache[2] = 0; + BrightnessCache[3] = 0; + + for (int i = 0; i < Repeat + 1; i++) + { + int32 btemp[4]; + + if ((i * CumulativeTime) >= MaxTime) + break; + + btemp[1] = (i * CumulativeTime) + BRTA; + if (btemp[1] > MaxTime) + btemp[1] = MaxTime; + btemp[1] -= (i * CumulativeTime); + if (btemp[1] < 0) + btemp[1] = 0; + + btemp[2] = (i * CumulativeTime) + BRTA + 1 + BRTB; + if (btemp[2] > MaxTime) + btemp[2] = MaxTime; + btemp[2] -= (i * CumulativeTime) + BRTA + 1; + if (btemp[2] < 0) + btemp[2] = 0; + + //btemp[3] = (i * CumulativeTime) + BRTA + 1 + BRTB + 1 + BRTC; + //if(btemp[3] > MaxTime) + // btemp[3] = MaxTime; + //btemp[3] -= (i * CumulativeTime); + //if(btemp[3] < 0) + // btemp[3] = 0; + + btemp[3] = (i * CumulativeTime) + BRTA + BRTB + BRTC + 1; + if (btemp[3] > MaxTime) + btemp[3] = MaxTime; + btemp[3] -= (i * CumulativeTime) + 1; + if (btemp[3] < 0) + btemp[3] = 0; + + BrightnessCache[1] += btemp[1]; + BrightnessCache[2] += btemp[2]; + BrightnessCache[3] += btemp[3]; + } + + //printf("BC: %d %d %d %d\n", BrightnessCache[0], BrightnessCache[1], BrightnessCache[2], BrightnessCache[3]); + + for (int lr = 0; lr < 2; lr++) + for (int i = 0; i < 4; i++) + { + BrightCLUT[lr][i] = ColorLUT[lr][BrightnessCache[i]]; + //printf("%d %d, %08x\n", lr, i, BrightCLUT[lr][i]); + } +} + +static void Recalc3DModeStuff(bool non_rgb_output = false) +{ + switch (VB3DMode) + { + default: + if (((Anaglyph_Colors[0] & 0xFF) && (Anaglyph_Colors[1] & 0xFF)) || + ((Anaglyph_Colors[0] & 0xFF00) && (Anaglyph_Colors[1] & 0xFF00)) || + ((Anaglyph_Colors[0] & 0xFF0000) && (Anaglyph_Colors[1] & 0xFF0000)) || + non_rgb_output) + { + CopyFBColumnToTarget = CopyFBColumnToTarget_AnaglyphSlow; + } + else + CopyFBColumnToTarget = CopyFBColumnToTarget_Anaglyph; + break; + + case VB3DMODE_CSCOPE: + CopyFBColumnToTarget = CopyFBColumnToTarget_CScope; + break; + + case VB3DMODE_SIDEBYSIDE: + CopyFBColumnToTarget = CopyFBColumnToTarget_SideBySide; + break; + + case VB3DMODE_VLI: + CopyFBColumnToTarget = CopyFBColumnToTarget_VLI; + break; + + case VB3DMODE_HLI: + CopyFBColumnToTarget = CopyFBColumnToTarget_HLI; + break; + } + RecalcBrightnessCache(); +} + +void VIP_Set3DMode(uint32 mode, bool reverse, uint32 prescale, uint32 sbs_separation) +{ + VB3DMode = mode; + VB3DReverse = reverse ? 1 : 0; + VBPrescale = prescale; + VBSBS_Separation = sbs_separation; + + VidSettingsDirty = true; + + for (uint32 p = 0; p < 256; p++) + { + uint32 v; + uint8 s[4]; + + s[0] = (p >> 0) & 0x3; + s[1] = (p >> 2) & 0x3; + s[2] = (p >> 4) & 0x3; + s[3] = (p >> 6) & 0x3; + + v = 0; + for (unsigned int i = 0, shifty = 0; i < 4; i++) + { + for (unsigned int ps = 0; ps < prescale; ps++) + { + v |= s[i] << shifty; + shifty += 2; + } + } + + HLILUT[p] = v; + } +} + +void VIP_SetParallaxDisable(bool disabled) +{ + ParallaxDisabled = disabled; +} + +void VIP_SetDefaultColor(uint32 default_color) +{ + Default_Color = default_color; + + VidSettingsDirty = true; +} + +void VIP_SetLEDOnScale(float coeff) +{ + VBLEDOnScale = coeff; +} + +void VIP_SetAnaglyphColors(uint32 lcolor, uint32 rcolor) +{ + Anaglyph_Colors[0] = lcolor; + Anaglyph_Colors[1] = rcolor; + + VidSettingsDirty = true; +} + +static uint16 FRMCYC; + +static uint16 DPCTRL; +static bool DisplayActive; + +#define XPCTRL_XP_RST 0x0001 +#define XPCTRL_XP_EN 0x0002 +static uint16 XPCTRL; +static uint16 SBCMP; // Derived from XPCTRL + +static uint16 SPT[4]; // SPT0~SPT3, 5f848~5f84e +static uint16 GPLT[4]; +static uint8 GPLT_Cache[4][4]; + +static INLINE void Recalc_GPLT_Cache(int which) +{ + for (int i = 0; i < 4; i++) + GPLT_Cache[which][i] = (GPLT[which] >> (i * 2)) & 3; +} + +static uint16 JPLT[4]; +static uint8 JPLT_Cache[4][4]; + +static INLINE void Recalc_JPLT_Cache(int which) +{ + for (int i = 0; i < 4; i++) + JPLT_Cache[which][i] = (JPLT[which] >> (i * 2)) & 3; +} + +static uint16 BKCOL; + +// +// +// +static int32 CalcNextEvent(void); + +static int32 last_ts; + +static uint32 Column; +static int32 ColumnCounter; + +static int32 DisplayRegion; +static bool DisplayFB; + +static int32 GameFrameCounter; + +static int32 DrawingCounter; +static bool DrawingActive; +static bool DrawingFB; +static uint32 DrawingBlock; +static int32 SB_Latch; +static int32 SBOUT_InactiveTime; + +//static uint8 CTA_L, CTA_R; + +static void CheckIRQ(void) +{ + VBIRQ_Assert(VBIRQ_SOURCE_VIP, (bool)(InterruptEnable & InterruptPending)); + +#if 0 + printf("%08x\n", InterruptEnable & InterruptPending); + if((bool)(InterruptEnable & InterruptPending)) + puts("IRQ asserted"); + else + puts("IRQ not asserted"); +#endif +} + +void VIP_Init(void) +{ + ParallaxDisabled = false; + Anaglyph_Colors[0] = 0xFF0000; + Anaglyph_Colors[1] = 0x0000FF; + VB3DMode = VB3DMODE_ANAGLYPH; + Default_Color = 0xFFFFFF; + VB3DReverse = 0; + VBPrescale = 1; + VBSBS_Separation = 0; + + VidSettingsDirty = true; +} + +void VIP_Power(void) +{ + Repeat = 0; + SB_Latch = 0; + SBOUT_InactiveTime = -1; + last_ts = 0; + + Column = 0; + ColumnCounter = 259; + + DisplayRegion = 0; + DisplayFB = 0; + + GameFrameCounter = 0; + + DrawingCounter = 0; + DrawingActive = false; + DrawingFB = 0; + DrawingBlock = 0; + + DPCTRL = 2; + DisplayActive = false; + + memset(FB, 0, 0x6000 * 2 * 2); + memset(CHR_RAM, 0, 0x8000); + memset(DRAM, 0, 0x20000); + + InterruptPending = 0; + InterruptEnable = 0; + + BRTA = 0; + BRTB = 0; + BRTC = 0; + REST = 0; + + FRMCYC = 0; + + XPCTRL = 0; + SBCMP = 0; + + for (int i = 0; i < 4; i++) + { + SPT[i] = 0; + GPLT[i] = 0; + JPLT[i] = 0; + + Recalc_GPLT_Cache(i); + Recalc_JPLT_Cache(i); + } + + BKCOL = 0; +} + +static INLINE uint16 ReadRegister(int32 ×tamp, uint32 A) +{ + uint16 ret = 0; //0xFFFF; + + if (A & 1) + VIP_DBGMSG("Misaligned VIP Read: %08x", A); + + switch (A & 0xFE) + { + default: + VIP_DBGMSG("Unknown VIP register read: %08x", A); + break; + + case 0x00: + ret = InterruptPending; + break; + + case 0x02: + ret = InterruptEnable; + break; + + case 0x20: //printf("Read DPSTTS at %d\n", timestamp); + ret = DPCTRL & 0x702; + if ((DisplayRegion & 1) && DisplayActive) + { + unsigned int DPBSY = 1 << ((DisplayRegion >> 1) & 1); + + if (DisplayFB) + DPBSY <<= 2; + + ret |= DPBSY << 2; + } + //if(!(DisplayRegion & 1)) // FIXME? (Had to do it this way for Galactic Pinball...) + ret |= 1 << 6; + break; + + // Note: Upper bits of BRTA, BRTB, BRTC, and REST(?) are 0 when read(on real hardware) + case 0x24: + ret = BRTA; + break; + + case 0x26: + ret = BRTB; + break; + + case 0x28: + ret = BRTC; + break; + + case 0x2A: + ret = REST; + break; + + case 0x30: + ret = 0xFFFF; + break; + + case 0x40: + ret = XPCTRL & 0x2; + if (DrawingActive) + { + ret |= (1 + DrawingFB) << 2; + } + if (timestamp < SBOUT_InactiveTime) + { + ret |= 0x8000; + ret |= /*DrawingBlock*/ SB_Latch << 8; + } + break; // XPSTTS, read-only + + case 0x44: + ret = 2; // VIP version. 2 is a known valid version, while the validity of other numbers is unknown, so we'll just go with 2. + break; + + case 0x48: + case 0x4a: + case 0x4c: + case 0x4e: + ret = SPT[(A >> 1) & 3]; + break; + + case 0x60: + case 0x62: + case 0x64: + case 0x66: + ret = GPLT[(A >> 1) & 3]; + break; + + case 0x68: + case 0x6a: + case 0x6c: + case 0x6e: + ret = JPLT[(A >> 1) & 3]; + break; + + case 0x70: + ret = BKCOL; + break; + } + + return (ret); +} + +static INLINE void WriteRegister(int32 ×tamp, uint32 A, uint16 V) +{ + if (A & 1) + VIP_DBGMSG("Misaligned VIP Write: %08x %04x", A, V); + + switch (A & 0xFE) + { + default: + VIP_DBGMSG("Unknown VIP register write: %08x %04x", A, V); + break; + + case 0x00: + break; // Interrupt pending, read-only + + case 0x02: + { + InterruptEnable = V & 0xE01F; + + VIP_DBGMSG("Interrupt Enable: %04x", V); + + if (V & 0x2000) + VIP_DBGMSG("Warning: VIP SB Hit Interrupt enable: %04x\n", V); + CheckIRQ(); + } + break; + + case 0x04: + InterruptPending &= ~V; + CheckIRQ(); + break; + + case 0x20: + break; // Display control, read-only. + + case 0x22: + DPCTRL = V & (0x703); // Display-control, write-only + if (V & 1) + { + DisplayActive = false; + InterruptPending &= ~(INT_TIME_ERR | INT_FRAME_START | INT_GAME_START | INT_RFB_END | INT_LFB_END | INT_SCAN_ERR); + CheckIRQ(); + } + break; + + case 0x24: + BRTA = V & 0xFF; // BRTA + RecalcBrightnessCache(); + break; + + case 0x26: + BRTB = V & 0xFF; // BRTB + RecalcBrightnessCache(); + break; + + case 0x28: + BRTC = V & 0xFF; // BRTC + RecalcBrightnessCache(); + break; + + case 0x2A: + REST = V & 0xFF; // REST + RecalcBrightnessCache(); + break; + + case 0x2E: + FRMCYC = V & 0xF; // FRMCYC, write-only? + break; + + case 0x30: + break; // CTA, read-only( + + case 0x40: + break; // XPSTTS, read-only + + case 0x42: + XPCTRL = V & 0x0002; // XPCTRL, write-only + SBCMP = (V >> 8) & 0x1F; + + if (V & 1) + { + VIP_DBGMSG("XPRST"); + DrawingActive = 0; + DrawingCounter = 0; + InterruptPending &= ~(INT_SB_HIT | INT_XP_END | INT_TIME_ERR); + CheckIRQ(); + } + break; + + case 0x44: + break; // Version Control, read-only? + + case 0x48: + case 0x4a: + case 0x4c: + case 0x4e: + SPT[(A >> 1) & 3] = V & 0x3FF; + break; + + case 0x60: + case 0x62: + case 0x64: + case 0x66: + GPLT[(A >> 1) & 3] = V & 0xFC; + Recalc_GPLT_Cache((A >> 1) & 3); + break; + + case 0x68: + case 0x6a: + case 0x6c: + case 0x6e: + JPLT[(A >> 1) & 3] = V & 0xFC; + Recalc_JPLT_Cache((A >> 1) & 3); + break; + + case 0x70: + BKCOL = V & 0x3; + break; + } +} + +// +// Don't update the VIP state on reads/writes, the event system will update it with enough precision as far as VB software cares. +// + +MDFN_FASTCALL uint8 VIP_Read8(int32 ×tamp, uint32 A) +{ + uint8 ret = 0; //0xFF; + + //VIP_Update(timestamp); + + switch (A >> 16) + { + case 0x0: + case 0x1: + if ((A & 0x7FFF) >= 0x6000) + { + ret = ne16_rbo_le(CHR_RAM, (A & 0x1FFF) | ((A >> 2) & 0x6000)); + } + else + { + ret = FB[(A >> 15) & 1][(A >> 16) & 1][A & 0x7FFF]; + } + break; + + case 0x2: + case 0x3: + ret = ne16_rbo_le(DRAM, A & 0x1FFFF); + break; + + case 0x4: + case 0x5: + if (A >= 0x5E000) + ret = ReadRegister(timestamp, A); + else + VIP_DBGMSG("Unknown VIP Read: %08x", A); + break; + + case 0x6: + break; + + case 0x7: + if (A >= 0x8000) + { + ret = ne16_rbo_le(CHR_RAM, A & 0x7FFF); + } + else + VIP_DBGMSG("Unknown VIP Read: %08x", A); + break; + + default: + VIP_DBGMSG("Unknown VIP Read: %08x", A); + break; + } + + //VB_SetEvent(VB_EVENT_VIP, timestamp + CalcNextEvent()); + + return (ret); +} + +MDFN_FASTCALL uint16 VIP_Read16(int32 ×tamp, uint32 A) +{ + uint16 ret = 0; //0xFFFF; + + //VIP_Update(timestamp); + + switch (A >> 16) + { + case 0x0: + case 0x1: + if ((A & 0x7FFF) >= 0x6000) + { + ret = ne16_rbo_le(CHR_RAM, (A & 0x1FFF) | ((A >> 2) & 0x6000)); + } + else + { + ret = MDFN_de16lsb(&FB[(A >> 15) & 1][(A >> 16) & 1][A & 0x7FFF]); + } + break; + + case 0x2: + case 0x3: + ret = ne16_rbo_le(DRAM, A & 0x1FFFF); + break; + + case 0x4: + case 0x5: + if (A >= 0x5E000) + ret = ReadRegister(timestamp, A); + else + VIP_DBGMSG("Unknown VIP Read: %08x", A); + break; + + case 0x6: + break; + + case 0x7: + if (A >= 0x8000) + { + ret = ne16_rbo_le(CHR_RAM, A & 0x7FFF); + } + else + VIP_DBGMSG("Unknown VIP Read: %08x", A); + break; + + default: + VIP_DBGMSG("Unknown VIP Read: %08x", A); + break; + } + + //VB_SetEvent(VB_EVENT_VIP, timestamp + CalcNextEvent()); + return (ret); +} + +MDFN_FASTCALL void VIP_Write8(int32 ×tamp, uint32 A, uint8 V) +{ + //VIP_Update(timestamp); + + //if(A >= 0x3DC00 && A < 0x3E000) + // printf("%08x %02x\n", A, V); + + switch (A >> 16) + { + case 0x0: + case 0x1: + if ((A & 0x7FFF) >= 0x6000) + ne16_wbo_le(CHR_RAM, (A & 0x1FFF) | ((A >> 2) & 0x6000), V); + else + FB[(A >> 15) & 1][(A >> 16) & 1][A & 0x7FFF] = V; + break; + + case 0x2: + case 0x3: + ne16_wbo_le(DRAM, A & 0x1FFFF, V); + break; + + case 0x4: + case 0x5: + if (A >= 0x5E000) + WriteRegister(timestamp, A, V); + else + VIP_DBGMSG("Unknown VIP Write: %08x %02x", A, V); + break; + + case 0x6: + VIP_DBGMSG("Unknown VIP Write: %08x %02x", A, V); + break; + + case 0x7: + if (A >= 0x8000) + ne16_wbo_le(CHR_RAM, A & 0x7FFF, V); + else + VIP_DBGMSG("Unknown VIP Write: %08x %02x", A, V); + break; + + default: + VIP_DBGMSG("Unknown VIP Write: %08x %02x", A, V); + break; + } + + //VB_SetEvent(VB_EVENT_VIP, timestamp + CalcNextEvent()); +} + +MDFN_FASTCALL void VIP_Write16(int32 ×tamp, uint32 A, uint16 V) +{ + //VIP_Update(timestamp); + + //if(A >= 0x3DC00 && A < 0x3E000) + // printf("%08x %04x\n", A, V); + + switch (A >> 16) + { + case 0x0: + case 0x1: + if ((A & 0x7FFF) >= 0x6000) + ne16_wbo_le(CHR_RAM, (A & 0x1FFF) | ((A >> 2) & 0x6000), V); + else + MDFN_en16lsb(&FB[(A >> 15) & 1][(A >> 16) & 1][A & 0x7FFF], V); + break; + + case 0x2: + case 0x3: + ne16_wbo_le(DRAM, A & 0x1FFFF, V); + break; + + case 0x4: + case 0x5: + if (A >= 0x5E000) + WriteRegister(timestamp, A, V); + else + VIP_DBGMSG("Unknown VIP Write: %08x %04x", A, V); + break; + + case 0x6: + VIP_DBGMSG("Unknown VIP Write: %08x %04x", A, V); + break; + + case 0x7: + if (A >= 0x8000) + ne16_wbo_le(CHR_RAM, A & 0x7FFF, V); + else + VIP_DBGMSG("Unknown VIP Write: %08x %04x", A, V); + break; + + default: + VIP_DBGMSG("Unknown VIP Write: %08x %04x", A, V); + break; + } + + //VB_SetEvent(VB_EVENT_VIP, timestamp + CalcNextEvent()); +} + +static MDFN_Surface real_surface; +static MDFN_Surface *surface; + +void VIP_StartFrame(MyFrameInfo* frame) +{ + // puts("Start frame"); + + if (VidSettingsDirty) + { + MakeColorLUT(); + Recalc3DModeStuff(); + + VidSettingsDirty = false; + } + + switch (VB3DMode) + { + default: + frame->Width = 384; + frame->Height = 224; + break; + + case VB3DMODE_VLI: + frame->Width = 768 * VBPrescale; + frame->Height = 224; + break; + + case VB3DMODE_HLI: + frame->Width = 384; + frame->Height = 448 * VBPrescale; + break; + + case VB3DMODE_CSCOPE: + frame->Width = 512; + frame->Height = 384; + break; + + case VB3DMODE_SIDEBYSIDE: + frame->Width = 768 + VBSBS_Separation; + frame->Height = 224; + break; + } + + surface = &real_surface; + real_surface.pixels = frame->VideoBuffer; + real_surface.pitch32 = frame->Width; +} + +void VIP_ResetTS(void) +{ + if (SBOUT_InactiveTime >= 0) + SBOUT_InactiveTime -= last_ts; + last_ts = 0; +} + +static int32 CalcNextEvent(void) +{ + return (ColumnCounter); +} + +#include "vip_draw.inc" + +static INLINE void CopyFBColumnToTarget_Anaglyph_BASE(const bool DisplayActive_arg, const int lr) +{ + const int fb = DisplayFB; + uint32 *target = surface->pixels + Column; + const int32 pitch32 = surface->pitch32; + const uint8 *fb_source = &FB[fb][lr][64 * Column]; + + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + uint32 pixel = BrightCLUT[lr][source_bits & 3]; + + if (!DisplayActive_arg) + pixel = 0; + + if (lr) + *target |= pixel; + else + *target = pixel; + + source_bits >>= 2; + target += pitch32; + } + fb_source++; + } +} + +static void CopyFBColumnToTarget_Anaglyph(void) +{ + const int lr = (DisplayRegion & 2) >> 1; + + if (!DisplayActive) + { + if (!lr) + CopyFBColumnToTarget_Anaglyph_BASE(0, 0); + else + CopyFBColumnToTarget_Anaglyph_BASE(0, 1); + } + else + { + if (!lr) + CopyFBColumnToTarget_Anaglyph_BASE(1, 0); + else + CopyFBColumnToTarget_Anaglyph_BASE(1, 1); + } +} + +static uint32 AnaSlowBuf[384][224]; + +static INLINE void CopyFBColumnToTarget_AnaglyphSlow_BASE(const bool DisplayActive_arg, const int lr) +{ + const int fb = DisplayFB; + const uint8 *fb_source = &FB[fb][lr][64 * Column]; + + if (!lr) + { + uint32 *target = AnaSlowBuf[Column]; + + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + uint32 pixel = BrightnessCache[source_bits & 3]; + + if (!DisplayActive_arg) + pixel = 0; + + *target = pixel; + source_bits >>= 2; + target++; + } + fb_source++; + } + } + else + { + uint32 *target = surface->pixels + Column; + const uint32 *left_src = AnaSlowBuf[Column]; + const int32 pitch32 = surface->pitch32; + + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + uint32 pixel = AnaSlowColorLUT[*left_src][DisplayActive_arg ? BrightnessCache[source_bits & 3] : 0]; + + *target = pixel; + + source_bits >>= 2; + target += pitch32; + left_src++; + } + fb_source++; + } + } +} + +static void CopyFBColumnToTarget_AnaglyphSlow(void) +{ + const int lr = (DisplayRegion & 2) >> 1; + + if (!DisplayActive) + { + if (!lr) + CopyFBColumnToTarget_AnaglyphSlow_BASE(0, 0); + else + CopyFBColumnToTarget_AnaglyphSlow_BASE(0, 1); + } + else + { + if (!lr) + CopyFBColumnToTarget_AnaglyphSlow_BASE(1, 0); + else + CopyFBColumnToTarget_AnaglyphSlow_BASE(1, 1); + } +} + +static void CopyFBColumnToTarget_CScope_BASE(const bool DisplayActive_arg, const int lr, const int dest_lr) +{ + const int fb = DisplayFB; + uint32 *target = surface->pixels + (dest_lr ? 512 - 16 - 1 : 16) + (dest_lr ? Column : 383 - Column) * surface->pitch32; + const uint8 *fb_source = &FB[fb][lr][64 * Column]; + + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + if (DisplayActive_arg) + *target = BrightCLUT[lr][source_bits & 3]; + else + *target = 0; + + source_bits >>= 2; + if (dest_lr) + target--; + else + target++; + } + fb_source++; + } +} + +static void CopyFBColumnToTarget_CScope(void) +{ + const int lr = (DisplayRegion & 2) >> 1; + + if (!DisplayActive) + { + if (!lr) + CopyFBColumnToTarget_CScope_BASE(0, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_CScope_BASE(0, 1, 1 ^ VB3DReverse); + } + else + { + if (!lr) + CopyFBColumnToTarget_CScope_BASE(1, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_CScope_BASE(1, 1, 1 ^ VB3DReverse); + } +} + +static void CopyFBColumnToTarget_SideBySide_BASE(const bool DisplayActive_arg, const int lr, const int dest_lr) +{ + const int fb = DisplayFB; + uint32 *target = surface->pixels + Column + (dest_lr ? (384 + VBSBS_Separation) : 0); + const int32 pitch32 = surface->pitch32; + const uint8 *fb_source = &FB[fb][lr][64 * Column]; + + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + if (DisplayActive_arg) + *target = BrightCLUT[lr][source_bits & 3]; + else + *target = 0; + source_bits >>= 2; + target += pitch32; + } + fb_source++; + } +} + +static void CopyFBColumnToTarget_SideBySide(void) +{ + const int lr = (DisplayRegion & 2) >> 1; + + if (!DisplayActive) + { + if (!lr) + CopyFBColumnToTarget_SideBySide_BASE(0, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_SideBySide_BASE(0, 1, 1 ^ VB3DReverse); + } + else + { + if (!lr) + CopyFBColumnToTarget_SideBySide_BASE(1, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_SideBySide_BASE(1, 1, 1 ^ VB3DReverse); + } +} + +static INLINE void CopyFBColumnToTarget_VLI_BASE(const bool DisplayActive_arg, const int lr, const int dest_lr) +{ + const int fb = DisplayFB; + uint32 *target = surface->pixels + Column * 2 * VBPrescale + dest_lr; + const int32 pitch32 = surface->pitch32; + const uint8 *fb_source = &FB[fb][lr][64 * Column]; + + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + uint32 tv; + + if (DisplayActive_arg) + tv = BrightCLUT[lr][source_bits & 3]; + else + tv = 0; + + for (uint32 ps = 0; ps < VBPrescale; ps++) + target[ps * 2] = tv; + + source_bits >>= 2; + target += pitch32; + } + fb_source++; + } +} + +static void CopyFBColumnToTarget_VLI(void) +{ + const int lr = (DisplayRegion & 2) >> 1; + + if (!DisplayActive) + { + if (!lr) + CopyFBColumnToTarget_VLI_BASE(0, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_VLI_BASE(0, 1, 1 ^ VB3DReverse); + } + else + { + if (!lr) + CopyFBColumnToTarget_VLI_BASE(1, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_VLI_BASE(1, 1, 1 ^ VB3DReverse); + } +} + +static INLINE void CopyFBColumnToTarget_HLI_BASE(const bool DisplayActive_arg, const int lr, const int dest_lr) +{ + const int fb = DisplayFB; + const int32 pitch32 = surface->pitch32; + uint32 *target = surface->pixels + Column + dest_lr * pitch32; + const uint8 *fb_source = &FB[fb][lr][64 * Column]; + + if (VBPrescale <= 4) + for (int y = 56; y; y--) + { + uint32 source_bits = HLILUT[*fb_source]; + + for (int y_sub = 4 * VBPrescale; y_sub; y_sub--) + { + if (DisplayActive_arg) + *target = BrightCLUT[lr][source_bits & 3]; + else + *target = 0; + + target += pitch32 * 2; + source_bits >>= 2; + } + fb_source++; + } + else + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + for (uint32 ps = 0; ps < VBPrescale; ps++) + { + if (DisplayActive_arg) + *target = BrightCLUT[lr][source_bits & 3]; + else + *target = 0; + + target += pitch32 * 2; + } + + source_bits >>= 2; + } + fb_source++; + } +} + +static void CopyFBColumnToTarget_HLI(void) +{ + const int lr = (DisplayRegion & 2) >> 1; + + if (!DisplayActive) + { + if (!lr) + CopyFBColumnToTarget_HLI_BASE(0, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_HLI_BASE(0, 1, 1 ^ VB3DReverse); + } + else + { + if (!lr) + CopyFBColumnToTarget_HLI_BASE(1, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_HLI_BASE(1, 1, 1 ^ VB3DReverse); + } +} + +v810_timestamp_t MDFN_FASTCALL VIP_Update(const v810_timestamp_t timestamp) +{ + int32 clocks = timestamp - last_ts; + int32 running_timestamp = timestamp; + + while (clocks > 0) + { + int32 chunk_clocks = clocks; + + if (DrawingCounter > 0 && chunk_clocks > DrawingCounter) + chunk_clocks = DrawingCounter; + if (chunk_clocks > ColumnCounter) + chunk_clocks = ColumnCounter; + + running_timestamp += chunk_clocks; + + if (DrawingCounter > 0) + { + DrawingCounter -= chunk_clocks; + if (DrawingCounter <= 0) + { + alignas(8) uint8 DrawingBuffers[2][512 * 8]; // Don't decrease this from 512 unless you adjust vip_draw.inc(including areas that draw off-visible >= 384 and >= -7 for speed reasons) + + VIP_DrawBlock(DrawingBlock, DrawingBuffers[0] + 8, DrawingBuffers[1] + 8); + + for (int lr = 0; lr < 2; lr++) + { + uint8 *FB_Target = FB[DrawingFB][lr] + DrawingBlock * 2; + + for (int x = 0; x < 384; x++) + { + FB_Target[64 * x + 0] = (DrawingBuffers[lr][8 + x + 512 * 0] << 0) | (DrawingBuffers[lr][8 + x + 512 * 1] << 2) | (DrawingBuffers[lr][8 + x + 512 * 2] << 4) | (DrawingBuffers[lr][8 + x + 512 * 3] << 6); + FB_Target[64 * x + 1] = (DrawingBuffers[lr][8 + x + 512 * 4] << 0) | (DrawingBuffers[lr][8 + x + 512 * 5] << 2) | (DrawingBuffers[lr][8 + x + 512 * 6] << 4) | (DrawingBuffers[lr][8 + x + 512 * 7] << 6); + } + } + + SBOUT_InactiveTime = running_timestamp + 1120; + SB_Latch = DrawingBlock; // Not exactly correct, but probably doesn't matter. + + DrawingBlock++; + if (DrawingBlock == 28) + { + DrawingActive = false; + + InterruptPending |= INT_XP_END; + CheckIRQ(); + } + else + DrawingCounter += 1120 * 4; + } + } + + ColumnCounter -= chunk_clocks; + if (ColumnCounter == 0) + { + if (DisplayRegion & 1) + { + if (!(Column & 3)) + { + const int lr = (DisplayRegion & 2) >> 1; + uint16 ctdata = ne16_rbo_le(DRAM, 0x1DFFE - ((Column >> 2) * 2) - (lr ? 0 : 0x200)); + + //printf("%02x, repeat: %02x\n", ctdata & 0xFF, ctdata >> 8); + + if ((ctdata >> 8) != Repeat) + { + Repeat = ctdata >> 8; + RecalcBrightnessCache(); + } + } + CopyFBColumnToTarget(); + } + + ColumnCounter = 259; + Column++; + if (Column == 384) + { + Column = 0; + + if (DisplayActive) + { + if (DisplayRegion & 1) // Did we just finish displaying an active region? + { + if (DisplayRegion & 2) // finished displaying right eye + InterruptPending |= INT_RFB_END; + else // Otherwise, left eye + InterruptPending |= INT_LFB_END; + + CheckIRQ(); + } + } + + DisplayRegion = (DisplayRegion + 1) & 3; + + if (DisplayRegion == 0) // New frame start + { + DisplayActive = DPCTRL & 0x2; + + if (DisplayActive) + { + InterruptPending |= INT_FRAME_START; + CheckIRQ(); + } + GameFrameCounter++; + if (GameFrameCounter > FRMCYC) // New game frame start? + { + InterruptPending |= INT_GAME_START; + CheckIRQ(); + + if (XPCTRL & XPCTRL_XP_EN) + { + DisplayFB ^= 1; + + DrawingBlock = 0; + DrawingActive = true; + DrawingCounter = 1120 * 4; + DrawingFB = DisplayFB ^ 1; + } + + GameFrameCounter = 0; + } + + VB_ExitLoop(); + } + } + } + + clocks -= chunk_clocks; + } + + last_ts = timestamp; + + return (timestamp + CalcNextEvent()); +} +} diff --git a/waterbox/vb/vip.h b/waterbox/vb/vip.h index 061e1c3425..c9cb9ab186 100644 --- a/waterbox/vb/vip.h +++ b/waterbox/vb/vip.h @@ -1,82 +1,82 @@ -/******************************************************************************/ -/* Mednafen Virtual Boy Emulation Module */ -/******************************************************************************/ -/* vip.h: -** Copyright (C) 2010-2016 Mednafen Team -** -** This program is free software; you can redistribute it and/or -** modify it under the terms of the GNU General Public License -** as published by the Free Software Foundation; either version 2 -** of the License, or (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software Foundation, Inc., -** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -#pragma once - -namespace MDFN_IEN_VB -{ -void VIP_Init(void) MDFN_COLD; -void VIP_Power(void) MDFN_COLD; - -void VIP_SetInstantDisplayHack(bool) MDFN_COLD; -void VIP_SetAllowDrawSkip(bool) MDFN_COLD; -void VIP_Set3DMode(uint32 mode, bool reverse, uint32 prescale, uint32 sbs_separation) MDFN_COLD; -void VIP_SetParallaxDisable(bool disabled) MDFN_COLD; -void VIP_SetDefaultColor(uint32 default_color) MDFN_COLD; -void VIP_SetAnaglyphColors(uint32 lcolor, uint32 rcolor) MDFN_COLD; // R << 16, G << 8, B << 0 -void VIP_SetLEDOnScale(float coeff) MDFN_COLD; - -v810_timestamp_t MDFN_FASTCALL VIP_Update(const v810_timestamp_t timestamp); -void VIP_ResetTS(void); - -void VIP_StartFrame(EmulateSpecStruct *espec); - -MDFN_FASTCALL uint8 VIP_Read8(v810_timestamp_t ×tamp, uint32 A); -MDFN_FASTCALL uint16 VIP_Read16(v810_timestamp_t ×tamp, uint32 A); - -MDFN_FASTCALL void VIP_Write8(v810_timestamp_t ×tamp, uint32 A, uint8 V); -MDFN_FASTCALL void VIP_Write16(v810_timestamp_t ×tamp, uint32 A, uint16 V); - -enum -{ - VIP_GSREG_IPENDING = 0, // Current pending interrupt(bits) - VIP_GSREG_IENABLE, - - VIP_GSREG_DPCTRL, - - VIP_GSREG_BRTA, - VIP_GSREG_BRTB, - VIP_GSREG_BRTC, - VIP_GSREG_REST, - VIP_GSREG_FRMCYC, - VIP_GSREG_XPCTRL, - - VIP_GSREG_SPT0, - VIP_GSREG_SPT1, - VIP_GSREG_SPT2, - VIP_GSREG_SPT3, - - VIP_GSREG_GPLT0, - VIP_GSREG_GPLT1, - VIP_GSREG_GPLT2, - VIP_GSREG_GPLT3, - - VIP_GSREG_JPLT0, - VIP_GSREG_JPLT1, - VIP_GSREG_JPLT2, - VIP_GSREG_JPLT3, - - VIP_GSREG_BKCOL, -}; - -uint32 VIP_GetRegister(const unsigned int id, char *special, const uint32 special_len); -void VIP_SetRegister(const unsigned int id, const uint32 value); -} +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* vip.h: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#pragma once + +namespace MDFN_IEN_VB +{ +void VIP_Init(void) MDFN_COLD; +void VIP_Power(void) MDFN_COLD; + +void VIP_SetInstantDisplayHack(bool) MDFN_COLD; +void VIP_SetAllowDrawSkip(bool) MDFN_COLD; +void VIP_Set3DMode(uint32 mode, bool reverse, uint32 prescale, uint32 sbs_separation) MDFN_COLD; +void VIP_SetParallaxDisable(bool disabled) MDFN_COLD; +void VIP_SetDefaultColor(uint32 default_color) MDFN_COLD; +void VIP_SetAnaglyphColors(uint32 lcolor, uint32 rcolor) MDFN_COLD; // R << 16, G << 8, B << 0 +void VIP_SetLEDOnScale(float coeff) MDFN_COLD; + +v810_timestamp_t MDFN_FASTCALL VIP_Update(const v810_timestamp_t timestamp); +void VIP_ResetTS(void); + +void VIP_StartFrame(MyFrameInfo* frame); + +MDFN_FASTCALL uint8 VIP_Read8(v810_timestamp_t ×tamp, uint32 A); +MDFN_FASTCALL uint16 VIP_Read16(v810_timestamp_t ×tamp, uint32 A); + +MDFN_FASTCALL void VIP_Write8(v810_timestamp_t ×tamp, uint32 A, uint8 V); +MDFN_FASTCALL void VIP_Write16(v810_timestamp_t ×tamp, uint32 A, uint16 V); + +enum +{ + VIP_GSREG_IPENDING = 0, // Current pending interrupt(bits) + VIP_GSREG_IENABLE, + + VIP_GSREG_DPCTRL, + + VIP_GSREG_BRTA, + VIP_GSREG_BRTB, + VIP_GSREG_BRTC, + VIP_GSREG_REST, + VIP_GSREG_FRMCYC, + VIP_GSREG_XPCTRL, + + VIP_GSREG_SPT0, + VIP_GSREG_SPT1, + VIP_GSREG_SPT2, + VIP_GSREG_SPT3, + + VIP_GSREG_GPLT0, + VIP_GSREG_GPLT1, + VIP_GSREG_GPLT2, + VIP_GSREG_GPLT3, + + VIP_GSREG_JPLT0, + VIP_GSREG_JPLT1, + VIP_GSREG_JPLT2, + VIP_GSREG_JPLT3, + + VIP_GSREG_BKCOL, +}; + +uint32 VIP_GetRegister(const unsigned int id, char *special, const uint32 special_len); +void VIP_SetRegister(const unsigned int id, const uint32 value); +}