最近在做纹理压缩工具, 以及数据包的生成.

shader编译已经在vs工程里面了, 使用custom build tool, build命令是调用BladeShaderComplier, 并且每个文件对应一个输出, vs会自动检查工程里面文件的依赖, 这样很方便.

纹理压缩如果也要放在visual studio里面, 可以用build event或者custom build step来做, 但是build dependency很难处理, 比如每个原始贴图对应一张目标贴图, 如果像编译shader那样, 把原始贴图全部加入到工程里面, 倒是可以解决, 但是原始贴图是随着美术制作不停增加的, 要美术把文件加入到vs工程里面感觉不太可行, 即使是程序员自己添加, 手动添加还是太繁琐, 除非写工具来生成vs project...

纹理压缩最好的方式是递归遍历源文件夹下所有文件并处理所有文件, 同时可以检查依赖的.

尝试了使用custom build, 用batch/cmd来处理纹理压缩, 遍历贴图目录处理所有文件, 依赖检查用时间戳比较, cmd只能比较字符串, 所以重新排列了文件字符串, 按年月日,AM/PM时间来排序,然后比较, 但是bat实在是太慢, 而且不同区域/语言,和版本(win7,win8)的系统获取的时间格式也不太一样, 觉得使用batch太麻烦, 毕竟batch不是shell, 功能太弱了.

所以考虑使用makefile来做. 首先看了微软的NMAKE的文档, 发现功能过于简单, 不能很方便的完成工作. 索性使用GNUmake, 这个make工具绝对可以胜任.

配置步骤:

1.首先下载cygwin, 安装时选择添加了make. 装完只把需要的文件放到svn/trunk/bin/tools/cygwin下面, 文件虽然有点多(绝大多数是shell命令), 但是只占用了33M的空间, 这个绝对值得拥有. 列一下我复制的那些文件:

/bin
/dev
/etc
/home
/lib
/tmp
/usr/share/terminfo

其中/usr/share/terminfo是用来做终端识别的, 这样手动进入cygwin bash可以处理backspace键, 为了手动调试用的.其中/bin占用了30M.其他的文件都很小可以忽略.

2.和编译shader文件一样, 把makefile当做vs的工程文件, 并把整个工程类型指定为Utility

3.把makefile加入到工程里面, 并把文件属性改为Custom build Tool

因为project内有文件指定了custom build type, 这个时候工程里面有了这个配置选项. 不填每个文件的Custom Build Tool, 而是切换到project, 在project scope配置custom build tool.这样对于工程内的所有文件, 都应用这一规则.

其中, Output是输出文件, 必须指定, 否则这个规则不会生效. %(Filename)是对应工程内的每个文件的(逐个文件). 但是跟shader不同的是, makefile没有对应的输出, 真正的输出是make产生的n个动态文件, 所以这里填了一个不存在, 也不可能生成的文件:$(OutDir)%(Filename).noexist , 这样vs在每次build检查时,发现目标不存在都会去执行build, 真正的依赖检查是在make中去做.

4.build command line, 就是调用cygwin的make来处理makefile:

 set Platform=$(Platform)
set Format=$(TargetExt)
set Path=.;$(SolutionDir)..\..\Bin\Tools\Release_Win32;$(SolutionDir)\..\..\Bin\Tools\cygwin\bin
set CHERE_INVOKING=1
set MAILCHECK=
cd %(RootDir)%(Directory) REM set path to unix style, not in makefile becase makefile must be compatible in no cygwin env
set OutDir=
for /F %%o in ( 'cygpath -u -p -a "$(OutDir)"' ) do (
set OutDir=%%o
) $(SolutionDir)\..\..\Bin\Tools\cygwin\bin\bash --login -c "make -j $(NUMBER_OF_PROCESSORS) -r -R -s -f %(Filename)%(Extension)"

需要注意几点:

  • 设置的环境变量: Platform, Format, OutDir 是配合makefile工作的,跟具体的makefile相关;
  • 设置Path的原因是cygwin的bash需要执行的shell命令, ...\cywin\bin就是/bin路径, 另外加上blade的工具路径即BladeTexCompressor所在的路径. 这个Path路径使用windows风格, cygwin启动后会将它自动转换为冒号分隔的格式.
  • MAILCHECK是禁止邮件检查, 根据cygwin的文档可以加速启动, 这个其实没什么用, 因为我复制的是最小运行集合,应该没有类似的功能.
  • CHERE_INVOKING是直接切换到当前工作路径, cygwin bash的默认login会切换到/home/%user%路径, 当使用了CHERE_INVOKING, 会留在当前路径, 即调用cygwin之前所在的路径
  • OutDir是配置VS的路径, 这样通过配置VS工程, 就可以改变makefile的输出. 不过为了makefile的兼容, 把OutDir转换为*nix风格路径即/开头的路径, 这个转换可以放在makefile里面做, 但是为了makefile的最大兼容, 最好不在makefile里面调用cygpath, 因为cygpath只有cygwin才有. 在调用makfile之前转换好路径, 这样makefile本身就可以不经修改在*nix上执行. cygpath 的输入路径, 不管有没有空格, 都一定要用""括起来, 否则转换的结果不对.
  • %(RootDir)%(Directory)是对应每个文件的绝对路径. 注意%()的VS变量都是逐个针对单个文件而变化的, $()的VS变量是固定的变量
  • 通过bash -c来直接调用make, 不过需要注意的是make的参数要跟make一起用""括起来, 否则会被视为bash的参数
  • 关于parallel build, 既然make -j 已经可以处理, 就省去了对每个tool写并行逻辑的繁琐.

做完以上工作, 就可以开心的写makefile了.

 #!/usr/bin/env make -f

 #GNU makefile for textures compression

 ifndef OutDir
$(error $$(OutDir) not defined.)
endif ifndef Format
$(error $$(Format) not defined.)
endif ifndef Platform
$(error $$(Platform) not defined.)
endif ifndef SUBDIRS
$(error $$(SUBDIRS) not defined.)
endif ifndef MIPMAPS
MIPMAPS = -
endif ##############################################################################################
# env setup
############################################################################################## VPATH = $(shell find . -type d)
COMMA :=, SOURCETYPELIST := tga,bmp,png,dds
NORMALDIR := /normal/ TC = BladeTexCompressor
TCFLAGS = --target=$(Platform) --format=$(Format) --mipmaps=$(MIPMAPS) #--verbose --filter=$(SOURCETYPELIST) ##############################################################################################
# source files
############################################################################################## SOURCE_EXTENSION = $(subst $(COMMA), ,$(SOURCETYPELIST)) FINDFLAGS = -name $(foreach i,$(SOURCE_EXTENSION), "*.$(i)" -or -name) ""
SOURCEFILES = $(shell find $(SUBDIRS) $(FINDFLAGS) )
SOURCEFILES += $(shell find -maxdepth $(FINDFLAGS) ) TARGETFILES = $(addsuffix .$(Format),$(basename $(SOURCEFILES)))
TARGETFILES := $(addprefix $(OutDir),$(TARGETFILES)) ##############################################################################################
# rules
############################################################################################## all: $(TARGETFILES) define compress_rule
$(OutDir)%.$(Format) : %.$
@echo $$(TCFLAGS) $$< $$(subst $$(NORMALDIR),--normalmap,$$(findstring $$(NORMALDIR),$$<)) --output=$$(OutDir)$$(dir $$<)
@$$(TC) $$(TCFLAGS) $$< $$(subst $$(NORMALDIR),--normalmap,$$(findstring $$(NORMALDIR),$$<)) --output=$$(OutDir)$$(dir $$<)
endef $(foreach EXT,$(SOURCE_EXTENSION),$(eval $(call compress_rule,$(EXT))))

可以看到makefile在处理每个文件的时候都会把命令行参数打出来, 显示到VS的Output.这么做的原因是如果报错或者崩溃, 可以把改命令行复制到VS的调试选项直接调试工具.

另外对于法线贴图的压缩, BladeTexCompressor有--normalmap选项. 现在的处理方式是把法线贴图放到normal文件夹, 这样mekefie检测到路径中包含/normal/, 就会自动加上--normalmap选项.

比如terrain/normal/ 和model/normal/ 下面的所有贴图都会被压缩成法线贴图.

纹理压缩工具遇到的问题:

1.makefile传进的路径是/开头的unix路径, 而且C:\ 被映射成了/cygdrive/c/. 所以工具里面要兼容处理这种情况, 转成C:\并使用Blade的IArchive/IStream来加载. 这部分实际做的时候是放在ResourceManager里面处理cygwin的路径了, 只在windows下处理. 这么做的原因是对于所有工具都可以用了, 而且改动最小. 最好的是放在tool级别, blade所有的工具都使用了ToolApplication这个基类, 放在ToolApplication里面是最好的. 如果有时间会改一下.

2.blade的tool跟editor或者用户app一样, 都有配置文件, 默认是在../Config路径下. 如果启动路径是tool/bin即工具所在的路径, ../Config可以加载, 但是如果在别的路径调用(这种情况tool用的比较多) ../Config就不对了, 所以需要处理. 在IPlatfomManager里面添加了getProcessImagePath(), 即进程镜像文件所在的路径(windows下的GetModulePath() ), 并注册到ResourceManager中,作为app:/, 这样如果请求路径为相对路径, 但不存在(cwd:/), 会尝试app:/ 这样blade内置的路径又多了一个, 现在为: media:/, memory:/ ,  cwd:/, app:/ 另外应用程序层还会注册一个plugins:/ 主要是为了某些情况下插件路径的切换, 比如Android系统的.so路径问题, 另外为了方便也加入了configs:/ 路径.

3.为了考虑tool的用户配置, tool 也支持--config参数, 来启动GUI配置(虽然目前从来没有用过...), 所以tool application会加载UI插件. 而现在的UI插件包含了编辑器的UI和Splash等所有模块, 虽然不用到的话, 只加载也没有问题, 但是发现VS里F7 build的时候, VS的焦点一直在切换和闪烁. 本来以为是batch或者make的问题, 但是单独F5运行BladeTexCompressor, 也会有很高概率出现cmd窗口失去焦点然后又获取焦点的情况. 最后发现是UI插件每次都会创建MFC的Splash窗口,虽然创建后立即隐藏, 但是确定是他导致了cmd窗口焦点丢失. 修改resource中的dialog属性可以解决, 不过对于tool来说, 根本不需要splash, 所以做了简单的模式判断(line 12):

     //////////////////////////////////////////////////////////////////////////
void BladeUIPlugin::install()
{
#if BLADE_PLATFORM == BLADE_PLATFORM_WINDOWS
RegisterEditorUI(); if( Factory<IStartupOutput>::getSingleton().getNumRegiteredClasses() == )
RegisterSingleton(SplashOutput, IStartupOutput);
else
NameRegisterSingleton(SplashOutput, IStartupOutput, BTString("Win32MFCStartupOutput")); //avoid console window focus blink for tools
if( IEnvironmentManager::getSingleton().getVariable( ConstDef::EnvString::WORKING_MODE) != BTString("tool") )
{
AFX_MANAGE_STATE( ::AfxGetStaticModuleState() );
CSplashWindow* splash = BLADE_NEW CSplashWindow(NULL);
splash->Create(CSplashWindow::IDD, NULL);
splash->ShowWindow(SW_HIDE);
SplashOutput::getSingleton().initialize(splash);
} if( Factory<IMenuManager>::getSingleton().getNumRegiteredClasses() == )
RegisterSingleton(MenuManager,IMenuManager);
else
NameRegisterSingleton(MenuManager,IMenuManager,BTString("Win32MFCMenuManager")); if( Factory<IIconManager>::getSingleton().getNumRegiteredClasses() == )
RegisterSingleton(IconManager,IIconManager);
else
NameRegisterSingleton(IconManager,IIconManager,BTString("Win32MFCIconManager"));
IConfigManager::getSingleton().setConfigDialog(&DialogProxy); NameRegisterFactory(EditorChildWindow, IEditorWindow, IEditorWindow::DEFAULT_TYPE);
#endif
}

备忘: working mode是由ToolApplication设置为"tool"的, EditorApplication会将它设置为"editor", GameApplication会设置为"game".

4. parallel build (make -j) 的问题, 由于tool application在退出的时候会去写配置, 这样在多个进程同时运行的时候, 某个进程正在写配置, 另一个进程在读配置, 就会有冲突. 这个也很很好解决. 因为之前设计的思路是, 如果有 --config参数, 配置完以后, 程序是会直接退出不会运行的. 所以在命令行有--config(启动配置GUI)的时候, ToolApplication才会去写配置.

压缩贴图格式的选择:

windows下使用BCn压缩, 文件格式为dds, android/ios下使用ETC2/EAC压缩, 文件格式为ktx. 选择文件格式的原则是, 可以方便的借助三方工具预览文件, 这样的通用性更好, 如果用自定义文件格式, 那么预览不方便, 因为现在没有时间为blade写专门的texture viewer.

另外, 对于纹理默认压缩为3通道贴图( BC1 或者 ETC2 RGB), 如果原始贴图有alpha, 会给出警告并压缩为4通道. (BC3或者ETC2 RGBA)

如果输入是1通道, 则给出警告并压缩为单(R)通道. (BC4 或者 R11_EAC)

法线贴图的格式, 对于DX用BC5, GLES用RG11_EAC.两种格式都是2通道RG, 质量差不多, 都是高质量压缩, 比DXT5nm好. BC5的两个通道都是一样的压缩质量, 跟DXT5nm(DXT5 swizzle)的alpha通道压缩质量一样.

对于法线贴图, 如果输入是1通道(bump map), 会给出警告并转换为tangent space normal map (这个功能之前做过并在runtime用过).


添加一个KTX文件格式说明: https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/

文件打包:

使用blade的BPK打包.之前的BPK写的很简单, 只处理简单的情况, 即把单个文件夹(--data=../Data)全部打包, 其中../Data是相对于executable的路径. 现在已经加入了append模式, 这样可以选择性的追加某些文件夹.另外遇到一个reallocation的bug, 因为用到了数组和指针, 在append模式下, 会动态扩展数组的容量, 所以之前的指针变无效了.这个之前考虑的过于简单, 虽然BPK模块同时用于runtime和离线工具, 但是runtime是根据包信息直接reserve空间没有re-allocation, 而且离线模式也不支持append模式, 可以预计算数组大小. 所以直接用了指针, 但现在遇到问题了.目前做了简单的hard fix (line 14), 以后有时间用index替换指针.

     inline BPK_ENTRY*    BPKData::addFolder(BPK_ENTRY* parent, const TString& name, uint32 attrib, const FILE_TIME& time, const HSTREAM& package, bool bWriteTail)
{
assert( parent != NULL );
BPK_ENTRY* existing = this->findSubEntry(parent, name);
if( existing != NULL )
{
//assert(false);
return NULL;
} if( mEntryCount >= mCapacity )
{
assert( mEntryCount == mCapacity );
//note: entries maybe relocated, so parent changes
index_t idx = parent - &mEntries[];
this->reserveEntries(mEntryCount+(mEntryCount+)/);
parent = mEntries + idx;
}
...
}

数据路径:

贴图之前放在../Data/image下面作为最终数据, 现在放在source目录内部, 作为源文件.

之前的shader会直接生成到../Data/material/shader下面. 所以win32/x64/Android的生成数据会相互覆盖. 避免相互覆盖可以生成到不同的文件夹, 但是直接打包的话也不能把不同平台的所有文件都打包.

现在的文件存放路径如下:

../Data/DataPlatform/image/dx_gl 存放dds

../Data/DataPlatform/image/gles 存放ktx

../Data/DataPlatform/shader/dx9 存放dx9 shader

../Data/DataPlatform/shader/gles3 存放gles3 shader

以后可能会有dx11/dx12 shader等等

然后makefile中根据平台把对应的贴图和shader, append到BPK中的/image和/shader中去.

原始资源配置文件的image路径对应的是media:/image, 跟打包后的包文件对应(media:/对应包文件), 这样的资源配置文件对于所有平台都一样, 唯一的缺点是只能读取包文件, 不能读本地文件, 如果想在windows下读取本地文件, 那么需要修改资源配置文件的路径, 切换到../Data/DataPlatform下.

DataPlatform专门存放平台相关的数据, 目前有image(texture)和shader, 其他的文件都应该是跨平台的. --至于为什么叫image而不叫texture, 只是考虑到image更general, 对于所有类型的应用都合适的名字, 而不仅仅是3d应用和游戏..

之前的贴图文件的保存, 直接写在ImageBase里, ImageBase::loadDDS, ImageBase::saveDDS() 这样. 现在由于加了ktx, 并且考虑到以后的可能扩展, 单独写了IImageFile,负责读写贴图, DDS的代码直接挪过去,不用修改. 通过命令行的参数--format=dds/ktx, 把格式字符串作为工厂类型直接创建出IImageFile instance来读写IImage. 这样文件格式的处理也变得流程化了.

下面是打包的makefile:

#!/usr/bin/env make -f

#GNU makefile for package generation

ifndef OutDir
$(error $$(OutDir) not defined.)
endif ifndef OutFile
$(error $$(OutFile) not defined.)
endif ifndef Platform
$(error $$(Platform) not defined.)
endif ##############################################################################################
# env setup
############################################################################################## #VPATH = $(shell find . -type d) #pwd: Bin\Data
SOURCE_PLATFORM_ROOT = Data_Platform
SOURCE_FILES = $(shell find ./ -type d -name "*" -maxdepth )
SOURCE_FILES := $(subst $(SOURCE_PLATFORM_ROOT),,$(SOURCE_FILES))
SOURCE_FILES := $(filter-out ./,$(SOURCE_FILES)) #sub folders in SOURCE_PLATFORM_ROOT DataFolders_Win32 = image/dx_gl shader/dx9
DataFolders_x64 = $(DataFolders_Win32) DataFolders_Android = image/gles shader/gles3
DataFolders_iOS = $(DataFolders_Android) #check if new platform added
ifndef DataFolders_$(Platform)
$(error $$(DataFolders_$(Platform)) not defined.)
endif PACK = BPK
PACKFLAGS = ##############################################################################################
# source files
############################################################################################## #use by dependency only
PLATFORM_DIRS = $(DataFolders_$(Platform))
ROOT_FILES = $(shell find -maxdepth -type f -name "*") FIND_PLATFORM_FOLDERS = $(addprefix ./$(SOURCE_PLATFORM_ROOT)/,$(PLATFORM_DIRS)) $(SOURCE_FILES)
ALLFILES = $(shell find $(FIND_PLATFORM_FOLDERS) -type f -name "*")
ALLFILES += $(ROOT_FILES)
SOURCE_FILES += $(ROOT_FILES) ##############################################################################################
# rules
############################################################################################## all : $(OutDir)$(OutFile) $(OutDir)$(OutFile) : $(ALLFILES)
@echo $(PACKFLAGS) $(SOURCE_FILES) --output=$@
@$(PACK) $(PACKFLAGS) $(SOURCE_FILES) --output=$@
@$(foreach SUBDIR,$(PLATFORM_DIRS), echo $(PACKFLAFGS) $(SOURCE_PLATFORM_ROOT)/$(SUBDIR) --append --destpath=/$(dir $(SUBDIR)) --output=$@; )
@$(foreach SUBDIR,$(PLATFORM_DIRS), $(PACK) $(PACKFLAFGS) $(SOURCE_PLATFORM_ROOT)/$(SUBDIR) --append --destpath=/$(dir $(SUBDIR)) --output=$@; )

最后关于ETC2纹理压缩:

使用的是etcpack来压缩和解压. 本来想用PVRTexTool的, 但它只有压缩功能, 而per block的解压和压缩功能runtime也有用到, 所以只能加入etcpack的三方代码来处理.
虽然imagemanager提供了格式转换, 但是现在有了离线压缩, runtime现在应该不会用到了.

对于ETC/ETC2 他们都是4x4块压缩, 具体算法还没有时间看, 简单看了ETC2的pdf和代码, ETC2除了ETC的压缩模式以外, 还引入了H mode, T mode, Planar mode, 对于这些情况的颜色分布, 做单独的压缩模式处理, 以提高精度. 由于模式的选择是per-block的, 所以如果每个block都是ETC1 mode的时候, 贴图就是ETC1贴图, 所以ETC2可以兼容ETC1.

然而ETC2的压缩算法, 是尝试性的, 即对每个mode都做压缩, 然后解压出color, 再跟原始color比较计算误差, 选择误差最小的mode, 这导致压缩过程异常缓慢.比如目前测试的结果, 300张贴图, 压缩为dds, 在我的台式机i7 4核8线程下make -j 8, 需要20秒钟, 如果压缩为ETC2的ktx, 则需要2分钟.
如果在我的老笔记本T6670 双核的笔记本上跑make -j2, dds需要2分钟, ktx需要15分钟.

经过以上的处理, Win32/x64/Android都可以在Visual Studio里面一键编译出shader/texture和最终的数据包了.其中Win32和x64使用的是同一份数据包.以后Android和iOS以及Android64和iOS64都会用同一份数据包.

引擎设计跟踪(九.14.2g) 将GNUMake集成到Visual Studio的更多相关文章

  1. 引擎设计跟踪(九.14.2a) 导出插件问题修复和 Tangent Space 裂缝修复

    由于工作很忙, 近半年的业余时间没空搞了, 不过工作马上忙完了, 趁十一有时间修了一些小问题. 这次更新跟骨骼动画无关, 修复了一个之前的, 关于tangent space裂缝的问题: 引擎设计跟踪( ...

  2. 引擎设计跟踪(九.14.2i) Android GLES 3.0 完善

    最近把渲染设备对应的GLES的API填上了. 主要有IRenderDevice/IShader/ITexture/IGraphicsResourceManager/IIndexBuffer/IVert ...

  3. 引擎设计跟踪(九.14.2f) 最近更新: OpenGL ES & tools

    之前骨骼动画的IK暂时放一放, 最近在搞GLES的实现. 之前除了GLES没有实现, Android的代码移植已经完毕: [原]跨平台编程注意事项(三): window 到 android 的 移植 ...

  4. 引擎设计跟踪(九.14.2d) [翻译] shader的跨平台方案之2014

    Origin: http://aras-p.info/blog/2014/03/28/cross-platform-shaders-in-2014/ 简译 translation: 作者在2012年写 ...

  5. 引擎设计跟踪(九.14.3.4) mile stone 2 - model和fbx导入的补漏

    之前milestone2已经做完的工作, 现在趁有时间记下笔记. 1.设计 这里是指兼容3ds max导出/fbx格式转换等等一系列工作的设计. 最开始, Blade的3dsmax导出插件, 全部代码 ...

  6. 引擎设计跟踪(九.14.2 final) Inverse Kinematics: CCD 在Blade中的实现

    因为工作忙, 好久没有记笔记了, 但是有时候发现还得翻以前的笔记去看, 所以还是尽量记下来备忘. 关于IK, 读了一些paper, 觉得之前翻译的那篇, welman的paper (http://gr ...

  7. 引擎设计跟踪(九.14.2j) TableView工具填坑以及多国语言

    Blade的UI都是预定义的接口, 然后由插件来负责实现, 目前只有MFC的插件. 最近加上了TableView的视图, 用于一些文件的查看和编辑, 比如前面在文件包的笔记中提到需写一个package ...

  8. 引擎设计跟踪(九.14.2b) 骨骼动画基本完成

    首先贴一个介绍max的sdk和骨骼动画的文章, 虽然很早的文章, 但是很有用, 感谢前辈们的贡献: 3Ds MAX骨骼动画导出插件编写 1.Dual Quaternion 关于Dual Quatern ...

  9. 引擎设计跟踪(九.14.2h) 开发计划

    以后的开发计划: 完善game runtime code, 跑简单的demo目前只有编辑器的运行流程, 没有游戏/demo流程, 图形的测试主要在编辑器上测试, 现在需要测试android系统的图形, ...

随机推荐

  1. js构造函数,索引数组和属性的属性

    本文主要介绍和小结js的构造函数,关联数组的实现方式和使用,及不可变对象和它的实现方式及他们使用过程中要注意的点 <script> function p(){ var len=argume ...

  2. Thinkphp 获取当前url

    $_GET['_URL_'] 获取整个url,返回值是数组 $Think.MODULE_NAME 获取当前class的名称 $Think.ACTION_NAME 获取当前action的方法名称

  3. php 提示Warning: mysql_fetch_array() expects

    我的高度代码如下 include("conn.php"); if(!empty($_GET['id'])){         $sql="select * from ne ...

  4. PHP实现的一分页工具类代码

    总的页数是一个长度一定的木块,这把尺子在这个木块上滑动,前提,尺子的两端不能超出木块:D.发现这么一来要做的事情就是去找这个尺子在木块上的起始点,根据用户给传进来的page变量.哈哈,关键代码下面: ...

  5. How to move the user document folder to D disk[Windows 7]

    when you install windows 7 OS, the system ask for you enter username and password, then you have not ...

  6. jquery.validate新的写法(jquery.validate1.13.js)

    <script src="../js/jquery.js"></script> <script src="../js/jquery.vali ...

  7. 为了android sdk下载,必须修改hosts

    #Download 下载 203.208.46.146 dl.google.com 203.208.46.146 dl-ssl.google.com #Groups 203.208.46.146 gr ...

  8. sqlserver中查找长时间未提交事务

    无论是有意无意,如果事务在数据库中保持打开,则它会阻塞其他进程对修改后的数据进行操作.同样,对事务日志进行备份也只会截断不活动事务的那部分事务日志,所以打开的事务会导致日志变多(甚至达到物理限制),直 ...

  9. 使用dom4j技术对xml文件的基本操作

    1.pojo类:Notice package com.green.notice.storage; import java.util.ArrayList; import java.util.List; ...

  10. hdu 1381 Crazy Search

    题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=1381 Crazy Search Description Many people like to sol ...